Adds SPFx React Jest Testing sample (#507)
This commit is contained in:
parent
13954cc7bc
commit
e415148bc5
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.4.1",
|
||||
"libraryName": "react-jest-testing",
|
||||
"libraryId": "9e16e13b-8d8c-4b89-8de4-dd654c5b6740",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
# SharePoint Framework React Jest Testing sample #
|
||||
|
||||
## SPFx-Jest-Enzyme-Sinon unit testing starter kit
|
||||
|
||||
## Summary
|
||||
|
||||
This sample uses the popular [Jest Testing Framework](https://facebook.github.io/jest/) with a SPFx client side solution. It is a SPFx-Jest-Enzyme-Sinon starter kit so you can start writing and debugging unit tests in typescript for your SPFx solution.
|
||||
The setup includes unit tests examples, code coverage reports in different formats, visual studio code unit test debug configurations for typescript, setting a coverage threshold (gates) for continuous integration and continuous deployment scenarios.
|
||||
|
||||
## Visual Studio Code Typescript debugging support for the Jest unit tests
|
||||
|
||||
The Visual Studio Code launch.json has all the debug configurations needed to start debugging the unit tests for your SPFx solution.
|
||||
There is a _Jest All_ configuration that will execute all the tests on demand.
|
||||
There is also a _Jest Watch_ (watcher) configuration that **will let live execution or debugging only on the affected by a change unit tests** (if the solution is part of hg/git repo) and will provide immediate feedback if a test passes or fails on component code change. This is good option for Test Driven Development scenarios.
|
||||
|
||||
![SharePoint Framework Jest Visual Studio Code - debugging unit test](./assets/Jest-Typescript-VSCode-debugging.png)
|
||||
|
||||
## Sinonjs is included as mocking framework for the SPFx solution
|
||||
|
||||
The solution also includes [Sinonjs](http://sinonjs.org/) that can be used to spawn spies, stubs and mocks.
|
||||
|
||||
## Enzyme is included to extend to unit tests support for React Components
|
||||
|
||||
Enzyme is a testing utility for React that makes it easier to assert, manipulate, and traverse your React Components' output.
|
||||
|
||||
## Basic unit tests scenarios included to demonstrate how Jest, Sinon and Enzyme can be used to test the SPFx React components
|
||||
|
||||
I wrote several unit tests to demonstrate how all testing libraries can be used together to test a React component with business logic and external dependencies included. Examples for mocking promises, pnpjs calls, https calls and spying on methods included for a quick start in unit testing your SPFx solution.
|
||||
|
||||
## Built-in Jest code coverage
|
||||
|
||||
Jest uses [Istanbul](https://github.com/gotwarlost/istanbul) under the hood to produce various code coverage reports including live VS code terminal output. Such reports can be integrated in CI tools like VSTS (Visual Studio Team Services) or Jenkins.
|
||||
|
||||
![SharePoint Framework Jest tests code coverage reports](./assets/SPFx-jest-coverage.png)
|
||||
|
||||
### Jest coverage threshold for continuous deployment pipeline setups
|
||||
|
||||
Jest coverage thresholds are set to yield error and potentially fail a build or pre-build if there isn't 100% coverage on branches, functions, lines and statements together. The thresholds can be changed by altering the solution packages.json file where the Jest configuration is.
|
||||
|
||||
```JavaScript
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100,
|
||||
"statements": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Generates summary report in JUnit xml format so it can be integrated with VSTS and Jenkins
|
||||
|
||||
After the execution of the unit tests a summary report will be generated under the `./jest/summary-jest-junit.xml` path. Because it uses junit xml formatting most of the CI tools can show the summary on a dashboard. Having that is useful for reporting. That summary is generated by [jest-junit (npm module)](https://www.npmjs.com/package/jest-junit).
|
||||
|
||||
## Unit tests support for SPFx extensions
|
||||
|
||||
The sample uses SPFx web part, but the same setup applies for SPFx extensions and they can simply be added to the solution and unit tested the same way.
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/drop-1.4.1-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
|
||||
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Office 365 subscription with SharePoint Online.
|
||||
- SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-jest-testing | Velin Georgiev ( [@VelinGeorgiev](https://twitter.com/velingeorgiev) )
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
0.0.1|May 9, 2018 | Initial commit
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository.
|
||||
- Open the command line, navigate to the web part folder and execute:
|
||||
- `npm i`
|
||||
- `npm test` **(NOT gulp test)**
|
||||
|
||||
## Features
|
||||
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
- Using React for building SharePoint Framework client-side web parts.
|
||||
- Using Jest Testing Framework for SPFx unit tests.
|
||||
- Unit tests included to demonstrate how spies, mocks can be used.
|
||||
- The use of Enzyme to speed up React unit test productivity.
|
||||
- The use of [SharePoint Patterns and Practices Reusable Client-side Libraries (PnP Js)](https://github.com/pnp/pnpjs).
|
||||
- Generating unit test code coverage reports for continious integreation tools such as VSTS
|
||||
- Generating unit test summary reports for continious integreation tools such as VSTS
|
||||
|
||||
## Usefull links
|
||||
|
||||
- [Jest](https://facebook.github.io/jest/)
|
||||
- [Jest cheatsheet](https://devhints.io/jest)
|
||||
- [Sinonjs](http://sinonjs.org/)
|
||||
- [Enzyme](http://airbnb.io/enzyme/)
|
||||
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-jest-testing" />
|
||||
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"ice-cream-shop-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/iceCreamShop/IceCreamShopWebPart.js",
|
||||
"manifest": "./src/webparts/iceCreamShop/IceCreamShopWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"IceCreamShopWebPartStrings": "lib/webparts/iceCreamShop/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-jest-testing",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-jest-testing-client-side-solution",
|
||||
"id": "9e16e13b-8d8c-4b89-8de4-dd654c5b6740",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-jest-testing.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||
// Display errors as warnings
|
||||
"displayAsWarning": true,
|
||||
// The TSLint task may have been configured with several custom lint rules
|
||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||
// project). If true, this flag will deactivate any of these rules.
|
||||
"removeExistingRules": true,
|
||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||
"useDefaultConfigAsBase": false,
|
||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||
// which are active, other than the list of rules below.
|
||||
"lintConfig": {
|
||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-case": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"valid-typeof": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,244 @@
|
|||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
|
||||
<coverage lines-valid="59" lines-covered="57" line-rate="0.9661" branches-valid="12" branches-covered="12" branch-rate="1" timestamp="1525986947960" complexity="0" version="0.1">
|
||||
<sources>
|
||||
<source>C:\Projects\sp-dev-fx-webparts\samples\react-jest-testing</source>
|
||||
</sources>
|
||||
<packages>
|
||||
<package name="components" line-rate="1" branch-rate="1">
|
||||
<classes>
|
||||
<class name="IceCreamShop.tsx" filename="src\webparts\iceCreamShop\components\IceCreamShop.tsx" line-rate="1" branch-rate="1">
|
||||
<methods>
|
||||
<method name="(anonymous_5)" hits="6" signature="()V">
|
||||
<lines>
|
||||
<line number="8" hits="6"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="IceCreamShop" hits="18" signature="()V">
|
||||
<lines>
|
||||
<line number="9" hits="18"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_7)" hits="18" signature="()V">
|
||||
<lines>
|
||||
<line number="21" hits="18"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_8)" hits="18" signature="()V">
|
||||
<lines>
|
||||
<line number="23" hits="18"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_9)" hits="18" signature="()V">
|
||||
<lines>
|
||||
<line number="24" hits="18"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_10)" hits="52" signature="()V">
|
||||
<lines>
|
||||
<line number="32" hits="52"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_11)" hits="132" signature="()V">
|
||||
<lines>
|
||||
<line number="42" hits="132"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_12)" hits="5" signature="()V">
|
||||
<lines>
|
||||
<line number="81" hits="5"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_13)" hits="5" signature="()V">
|
||||
<lines>
|
||||
<line number="83" hits="5"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_14)" hits="3" signature="()V">
|
||||
<lines>
|
||||
<line number="91" hits="3"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_15)" hits="2" signature="()V">
|
||||
<lines>
|
||||
<line number="98" hits="2"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_16)" hits="2" signature="()V">
|
||||
<lines>
|
||||
<line number="100" hits="2"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_17)" hits="2" signature="()V">
|
||||
<lines>
|
||||
<line number="107" hits="2"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_18)" hits="2" signature="()V">
|
||||
<lines>
|
||||
<line number="110" hits="2"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_19)" hits="5" signature="()V">
|
||||
<lines>
|
||||
<line number="117" hits="5"/>
|
||||
</lines>
|
||||
</method>
|
||||
</methods>
|
||||
<lines>
|
||||
<line number="1" hits="6" branch="false"/>
|
||||
<line number="2" hits="6" branch="false"/>
|
||||
<line number="8" hits="6" branch="false"/>
|
||||
<line number="9" hits="18" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="21" hits="18" branch="false"/>
|
||||
<line number="23" hits="18" branch="false"/>
|
||||
<line number="24" hits="18" branch="false"/>
|
||||
<line number="26" hits="18" branch="false"/>
|
||||
<line number="27" hits="18" branch="false"/>
|
||||
<line number="32" hits="52" branch="false"/>
|
||||
<line number="33" hits="52" branch="false"/>
|
||||
<line number="44" hits="132" branch="false"/>
|
||||
<line number="81" hits="6" branch="false"/>
|
||||
<line number="83" hits="5" branch="false"/>
|
||||
<line number="84" hits="5" branch="false"/>
|
||||
<line number="85" hits="5" branch="false"/>
|
||||
<line number="86" hits="5" branch="false"/>
|
||||
<line number="87" hits="5" branch="false"/>
|
||||
<line number="91" hits="6" branch="false"/>
|
||||
<line number="92" hits="3" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="93" hits="1" branch="false"/>
|
||||
<line number="96" hits="2" branch="false"/>
|
||||
<line number="97" hits="2" branch="false"/>
|
||||
<line number="98" hits="2" branch="false"/>
|
||||
<line number="100" hits="2" branch="false"/>
|
||||
<line number="101" hits="2" branch="false"/>
|
||||
<line number="102" hits="2" branch="false"/>
|
||||
<line number="107" hits="6" branch="false"/>
|
||||
<line number="108" hits="2" branch="false"/>
|
||||
<line number="110" hits="2" branch="false"/>
|
||||
<line number="111" hits="2" branch="false"/>
|
||||
<line number="112" hits="2" branch="false"/>
|
||||
<line number="113" hits="2" branch="false"/>
|
||||
<line number="117" hits="6" branch="false"/>
|
||||
<line number="118" hits="5" branch="true" condition-coverage="100% (2/2)"/>
|
||||
<line number="120" hits="6" branch="false"/>
|
||||
</lines>
|
||||
</class>
|
||||
</classes>
|
||||
</package>
|
||||
<package name="iceCreamProviders" line-rate="0.9129999999999999" branch-rate="1">
|
||||
<classes>
|
||||
<class name="IceCreamFakeProvider.ts" filename="src\webparts\iceCreamShop\iceCreamProviders\IceCreamFakeProvider.ts" line-rate="1" branch-rate="1">
|
||||
<methods>
|
||||
<method name="(anonymous_0)" hits="21" signature="()V">
|
||||
<lines>
|
||||
<line number="4" hits="21"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_2)" hits="16" signature="()V">
|
||||
<lines>
|
||||
<line number="6" hits="16"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_3)" hits="16" signature="()V">
|
||||
<lines>
|
||||
<line number="8" hits="16"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_4)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="21" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_5)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="23" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
</methods>
|
||||
<lines>
|
||||
<line number="4" hits="5" branch="false"/>
|
||||
<line number="6" hits="5" branch="false"/>
|
||||
<line number="8" hits="16" branch="false"/>
|
||||
<line number="17" hits="16" branch="false"/>
|
||||
<line number="21" hits="5" branch="false"/>
|
||||
<line number="23" hits="1" branch="false"/>
|
||||
<line number="25" hits="5" branch="false"/>
|
||||
</lines>
|
||||
</class>
|
||||
<class name="IceCreamPnPJsProvider.ts" filename="src\webparts\iceCreamShop\iceCreamProviders\IceCreamPnPJsProvider.ts" line-rate="0.875" branch-rate="1">
|
||||
<methods>
|
||||
<method name="(anonymous_12)" hits="2" signature="()V">
|
||||
<lines>
|
||||
<line number="5" hits="2"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="IceCreamPnPJsProvider" hits="4" signature="()V">
|
||||
<lines>
|
||||
<line number="9" hits="4"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_14)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="13" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_15)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="15" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_18)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="23" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_19)" hits="0" signature="()V">
|
||||
<lines>
|
||||
<line number="31" hits="0"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_20)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="35" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_21)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="37" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_22)" hits="1" signature="()V">
|
||||
<lines>
|
||||
<line number="42" hits="1"/>
|
||||
</lines>
|
||||
</method>
|
||||
<method name="(anonymous_23)" hits="0" signature="()V">
|
||||
<lines>
|
||||
<line number="43" hits="0"/>
|
||||
</lines>
|
||||
</method>
|
||||
</methods>
|
||||
<lines>
|
||||
<line number="5" hits="2" branch="false"/>
|
||||
<line number="10" hits="4" branch="false"/>
|
||||
<line number="13" hits="2" branch="false"/>
|
||||
<line number="15" hits="1" branch="false"/>
|
||||
<line number="23" hits="1" branch="false"/>
|
||||
<line number="25" hits="1" branch="false"/>
|
||||
<line number="26" hits="3" branch="false"/>
|
||||
<line number="27" hits="3" branch="false"/>
|
||||
<line number="30" hits="1" branch="false"/>
|
||||
<line number="31" hits="0" branch="false"/>
|
||||
<line number="35" hits="2" branch="false"/>
|
||||
<line number="37" hits="1" branch="false"/>
|
||||
<line number="38" hits="1" branch="false"/>
|
||||
<line number="42" hits="1" branch="false"/>
|
||||
<line number="43" hits="0" branch="false"/>
|
||||
<line number="46" hits="2" branch="false"/>
|
||||
</lines>
|
||||
</class>
|
||||
</classes>
|
||||
</package>
|
||||
</packages>
|
||||
</coverage>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,223 @@
|
|||
body, html {
|
||||
margin:0; padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
font-family: Helvetica Neue, Helvetica, Arial;
|
||||
font-size: 14px;
|
||||
color:#333;
|
||||
}
|
||||
.small { font-size: 12px; }
|
||||
*, *:after, *:before {
|
||||
-webkit-box-sizing:border-box;
|
||||
-moz-box-sizing:border-box;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
h1 { font-size: 20px; margin: 0;}
|
||||
h2 { font-size: 14px; }
|
||||
pre {
|
||||
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-moz-tab-size: 2;
|
||||
-o-tab-size: 2;
|
||||
tab-size: 2;
|
||||
}
|
||||
a { color:#0074D9; text-decoration:none; }
|
||||
a:hover { text-decoration:underline; }
|
||||
.strong { font-weight: bold; }
|
||||
.space-top1 { padding: 10px 0 0 0; }
|
||||
.pad2y { padding: 20px 0; }
|
||||
.pad1y { padding: 10px 0; }
|
||||
.pad2x { padding: 0 20px; }
|
||||
.pad2 { padding: 20px; }
|
||||
.pad1 { padding: 10px; }
|
||||
.space-left2 { padding-left:55px; }
|
||||
.space-right2 { padding-right:20px; }
|
||||
.center { text-align:center; }
|
||||
.clearfix { display:block; }
|
||||
.clearfix:after {
|
||||
content:'';
|
||||
display:block;
|
||||
height:0;
|
||||
clear:both;
|
||||
visibility:hidden;
|
||||
}
|
||||
.fl { float: left; }
|
||||
@media only screen and (max-width:640px) {
|
||||
.col3 { width:100%; max-width:100%; }
|
||||
.hide-mobile { display:none!important; }
|
||||
}
|
||||
|
||||
.quiet {
|
||||
color: #7f7f7f;
|
||||
color: rgba(0,0,0,0.5);
|
||||
}
|
||||
.quiet a { opacity: 0.7; }
|
||||
|
||||
.fraction {
|
||||
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||
font-size: 10px;
|
||||
color: #555;
|
||||
background: #E8E8E8;
|
||||
padding: 4px 5px;
|
||||
border-radius: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.path a:link, div.path a:visited { color: #333; }
|
||||
table.coverage {
|
||||
border-collapse: collapse;
|
||||
margin: 10px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.coverage td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.coverage td.line-count {
|
||||
text-align: right;
|
||||
padding: 0 5px 0 20px;
|
||||
}
|
||||
table.coverage td.line-coverage {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
min-width:20px;
|
||||
}
|
||||
|
||||
table.coverage td span.cline-any {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
width: 100%;
|
||||
}
|
||||
.missing-if-branch {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
padding: 0 4px;
|
||||
background: #333;
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
.skip-if-branch {
|
||||
display: none;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
padding: 0 4px;
|
||||
background: #ccc;
|
||||
color: white;
|
||||
}
|
||||
.missing-if-branch .typ, .skip-if-branch .typ {
|
||||
color: inherit !important;
|
||||
}
|
||||
.coverage-summary {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
||||
.keyline-all { border: 1px solid #ddd; }
|
||||
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
||||
.coverage-summary tbody { border: 1px solid #bbb; }
|
||||
.coverage-summary td { border-right: 1px solid #bbb; }
|
||||
.coverage-summary td:last-child { border-right: none; }
|
||||
.coverage-summary th {
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.coverage-summary th.file { border-right: none !important; }
|
||||
.coverage-summary th.pct { }
|
||||
.coverage-summary th.pic,
|
||||
.coverage-summary th.abs,
|
||||
.coverage-summary td.pct,
|
||||
.coverage-summary td.abs { text-align: right; }
|
||||
.coverage-summary td.file { white-space: nowrap; }
|
||||
.coverage-summary td.pic { min-width: 120px !important; }
|
||||
.coverage-summary tfoot td { }
|
||||
|
||||
.coverage-summary .sorter {
|
||||
height: 10px;
|
||||
width: 7px;
|
||||
display: inline-block;
|
||||
margin-left: 0.5em;
|
||||
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
||||
}
|
||||
.coverage-summary .sorted .sorter {
|
||||
background-position: 0 -20px;
|
||||
}
|
||||
.coverage-summary .sorted-desc .sorter {
|
||||
background-position: 0 -10px;
|
||||
}
|
||||
.status-line { height: 10px; }
|
||||
/* yellow */
|
||||
.cbranch-no { background: yellow !important; color: #111; }
|
||||
/* dark red */
|
||||
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
||||
.low .chart { border:1px solid #C21F39 }
|
||||
.highlighted,
|
||||
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
||||
background: #C21F39 !important;
|
||||
}
|
||||
/* medium red */
|
||||
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
||||
/* light red */
|
||||
.low, .cline-no { background:#FCE1E5 }
|
||||
/* light green */
|
||||
.high, .cline-yes { background:rgb(230,245,208) }
|
||||
/* medium green */
|
||||
.cstat-yes { background:rgb(161,215,106) }
|
||||
/* dark green */
|
||||
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
||||
.high .chart { border:1px solid rgb(77,146,33) }
|
||||
|
||||
.medium .chart { border:1px solid #666; }
|
||||
.medium .cover-fill { background: #666; }
|
||||
|
||||
.cstat-skip { background: #ddd; color: #111; }
|
||||
.fstat-skip { background: #ddd; color: #111 !important; }
|
||||
.cbranch-skip { background: #ddd !important; color: #111; }
|
||||
|
||||
span.cline-neutral { background: #eaeaea; }
|
||||
.medium { background: #eaeaea; }
|
||||
|
||||
.coverage-summary td.empty {
|
||||
opacity: .5;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
line-height: 1;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.cover-fill, .cover-empty {
|
||||
display:inline-block;
|
||||
height: 12px;
|
||||
}
|
||||
.chart {
|
||||
line-height: 0;
|
||||
}
|
||||
.cover-empty {
|
||||
background: white;
|
||||
}
|
||||
.cover-full {
|
||||
border-right: none !important;
|
||||
}
|
||||
pre.prettyprint {
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.com { color: #999 !important; }
|
||||
.ignore-none { color: #999; font-weight: normal; }
|
||||
|
||||
.wrapper {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
height: 100%;
|
||||
margin: 0 auto -48px;
|
||||
}
|
||||
.footer, .push {
|
||||
height: 48px;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
var jumpToCode = (function init () {
|
||||
// Classes of code we would like to highlight
|
||||
var missingCoverageClasses = [ '.cbranch-no', '.cstat-no', '.fstat-no' ];
|
||||
|
||||
// We don't want to select elements that are direct descendants of another match
|
||||
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
||||
|
||||
// Selecter that finds elements on the page to which we can jump
|
||||
var selector = notSelector + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
||||
|
||||
// The NodeList of matching elements
|
||||
var missingCoverageElements = document.querySelectorAll(selector);
|
||||
|
||||
var currentIndex;
|
||||
|
||||
function toggleClass(index) {
|
||||
missingCoverageElements.item(currentIndex).classList.remove('highlighted');
|
||||
missingCoverageElements.item(index).classList.add('highlighted');
|
||||
}
|
||||
|
||||
function makeCurrent(index) {
|
||||
toggleClass(index);
|
||||
currentIndex = index;
|
||||
missingCoverageElements.item(index)
|
||||
.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
|
||||
}
|
||||
|
||||
function goToPrevious() {
|
||||
var nextIndex = 0;
|
||||
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
||||
nextIndex = missingCoverageElements.length - 1;
|
||||
} else if (missingCoverageElements.length > 1) {
|
||||
nextIndex = currentIndex - 1;
|
||||
}
|
||||
|
||||
makeCurrent(nextIndex);
|
||||
}
|
||||
|
||||
function goToNext() {
|
||||
var nextIndex = 0;
|
||||
|
||||
if (typeof currentIndex === 'number' && currentIndex < (missingCoverageElements.length - 1)) {
|
||||
nextIndex = currentIndex + 1;
|
||||
}
|
||||
|
||||
makeCurrent(nextIndex);
|
||||
}
|
||||
|
||||
return function jump(event) {
|
||||
switch (event.which) {
|
||||
case 78: // n
|
||||
case 74: // j
|
||||
goToNext();
|
||||
break;
|
||||
case 66: // b
|
||||
case 75: // k
|
||||
case 80: // p
|
||||
goToPrevious();
|
||||
break;
|
||||
}
|
||||
};
|
||||
}());
|
||||
window.addEventListener('keydown', jumpToCode);
|
|
@ -0,0 +1,429 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Code coverage report for components/IceCreamShop.tsx</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="..\prettify.css" />
|
||||
<link rel="stylesheet" href="..\base.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(..\sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>
|
||||
<a href="..\index.html">All files</a> / <a href="index.html">components</a> IceCreamShop.tsx
|
||||
</h1>
|
||||
<div class='clearfix'>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>40/40</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>12/12</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>15/15</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>36/36</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a>
|
||||
<a name='L47'></a><a href='#L47'>47</a>
|
||||
<a name='L48'></a><a href='#L48'>48</a>
|
||||
<a name='L49'></a><a href='#L49'>49</a>
|
||||
<a name='L50'></a><a href='#L50'>50</a>
|
||||
<a name='L51'></a><a href='#L51'>51</a>
|
||||
<a name='L52'></a><a href='#L52'>52</a>
|
||||
<a name='L53'></a><a href='#L53'>53</a>
|
||||
<a name='L54'></a><a href='#L54'>54</a>
|
||||
<a name='L55'></a><a href='#L55'>55</a>
|
||||
<a name='L56'></a><a href='#L56'>56</a>
|
||||
<a name='L57'></a><a href='#L57'>57</a>
|
||||
<a name='L58'></a><a href='#L58'>58</a>
|
||||
<a name='L59'></a><a href='#L59'>59</a>
|
||||
<a name='L60'></a><a href='#L60'>60</a>
|
||||
<a name='L61'></a><a href='#L61'>61</a>
|
||||
<a name='L62'></a><a href='#L62'>62</a>
|
||||
<a name='L63'></a><a href='#L63'>63</a>
|
||||
<a name='L64'></a><a href='#L64'>64</a>
|
||||
<a name='L65'></a><a href='#L65'>65</a>
|
||||
<a name='L66'></a><a href='#L66'>66</a>
|
||||
<a name='L67'></a><a href='#L67'>67</a>
|
||||
<a name='L68'></a><a href='#L68'>68</a>
|
||||
<a name='L69'></a><a href='#L69'>69</a>
|
||||
<a name='L70'></a><a href='#L70'>70</a>
|
||||
<a name='L71'></a><a href='#L71'>71</a>
|
||||
<a name='L72'></a><a href='#L72'>72</a>
|
||||
<a name='L73'></a><a href='#L73'>73</a>
|
||||
<a name='L74'></a><a href='#L74'>74</a>
|
||||
<a name='L75'></a><a href='#L75'>75</a>
|
||||
<a name='L76'></a><a href='#L76'>76</a>
|
||||
<a name='L77'></a><a href='#L77'>77</a>
|
||||
<a name='L78'></a><a href='#L78'>78</a>
|
||||
<a name='L79'></a><a href='#L79'>79</a>
|
||||
<a name='L80'></a><a href='#L80'>80</a>
|
||||
<a name='L81'></a><a href='#L81'>81</a>
|
||||
<a name='L82'></a><a href='#L82'>82</a>
|
||||
<a name='L83'></a><a href='#L83'>83</a>
|
||||
<a name='L84'></a><a href='#L84'>84</a>
|
||||
<a name='L85'></a><a href='#L85'>85</a>
|
||||
<a name='L86'></a><a href='#L86'>86</a>
|
||||
<a name='L87'></a><a href='#L87'>87</a>
|
||||
<a name='L88'></a><a href='#L88'>88</a>
|
||||
<a name='L89'></a><a href='#L89'>89</a>
|
||||
<a name='L90'></a><a href='#L90'>90</a>
|
||||
<a name='L91'></a><a href='#L91'>91</a>
|
||||
<a name='L92'></a><a href='#L92'>92</a>
|
||||
<a name='L93'></a><a href='#L93'>93</a>
|
||||
<a name='L94'></a><a href='#L94'>94</a>
|
||||
<a name='L95'></a><a href='#L95'>95</a>
|
||||
<a name='L96'></a><a href='#L96'>96</a>
|
||||
<a name='L97'></a><a href='#L97'>97</a>
|
||||
<a name='L98'></a><a href='#L98'>98</a>
|
||||
<a name='L99'></a><a href='#L99'>99</a>
|
||||
<a name='L100'></a><a href='#L100'>100</a>
|
||||
<a name='L101'></a><a href='#L101'>101</a>
|
||||
<a name='L102'></a><a href='#L102'>102</a>
|
||||
<a name='L103'></a><a href='#L103'>103</a>
|
||||
<a name='L104'></a><a href='#L104'>104</a>
|
||||
<a name='L105'></a><a href='#L105'>105</a>
|
||||
<a name='L106'></a><a href='#L106'>106</a>
|
||||
<a name='L107'></a><a href='#L107'>107</a>
|
||||
<a name='L108'></a><a href='#L108'>108</a>
|
||||
<a name='L109'></a><a href='#L109'>109</a>
|
||||
<a name='L110'></a><a href='#L110'>110</a>
|
||||
<a name='L111'></a><a href='#L111'>111</a>
|
||||
<a name='L112'></a><a href='#L112'>112</a>
|
||||
<a name='L113'></a><a href='#L113'>113</a>
|
||||
<a name='L114'></a><a href='#L114'>114</a>
|
||||
<a name='L115'></a><a href='#L115'>115</a>
|
||||
<a name='L116'></a><a href='#L116'>116</a>
|
||||
<a name='L117'></a><a href='#L117'>117</a>
|
||||
<a name='L118'></a><a href='#L118'>118</a>
|
||||
<a name='L119'></a><a href='#L119'>119</a>
|
||||
<a name='L120'></a><a href='#L120'>120</a>
|
||||
<a name='L121'></a><a href='#L121'>121</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">18x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">18x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">18x</span>
|
||||
<span class="cline-any cline-yes">18x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">18x</span>
|
||||
<span class="cline-any cline-yes">18x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">52x</span>
|
||||
<span class="cline-any cline-yes">52x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">132x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">6x</span>
|
||||
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import * as React from 'react';
|
||||
import styles from './IceCreamShop.module.scss';
|
||||
import { IIceCreamShopProps } from './IIceCreamShopProps';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
import { IIceCreamShopState } from './IIceCreamShopState';
|
||||
import { IceCream } from '../iceCreamProviders/IceCream';
|
||||
|
||||
export default class IceCreamShop extends React.Component<IIceCreamShopProps, IIceCreamShopState> {
|
||||
constructor(props: IIceCreamShopProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
iceCreamFlavoursList: [],
|
||||
quantity: 1,
|
||||
selectedIceCream: null,
|
||||
totalPrice: 0,
|
||||
hasBoughtIceCream: false
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
|
||||
this.props.iceCreamProvider.getAll().then((result: IceCream[]) => {
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
|
||||
state.iceCreamFlavoursList = result;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IIceCreamShopProps> {
|
||||
return (
|
||||
<div className={styles.ic} id="iceCreamShop">
|
||||
<div className={styles.container}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column}>
|
||||
<h1 className={styles.title}>{this.props.strings.TitleLabel}</h1>
|
||||
<div id="iceCreamFlavoursList">
|
||||
{
|
||||
this.state.iceCreamFlavoursList &&
|
||||
this.state.iceCreamFlavoursList.map((item, index) => {
|
||||
|
||||
return <div key={index}>
|
||||
|
||||
<div className={styles.subTitle}>{item.Title}</div>
|
||||
|
||||
<button data-automationid={`item-${index}`} className={styles.button} onClick={this.selectHandler.bind(this, item)}>
|
||||
{this.props.strings.GetItLabel} {item.Price}
|
||||
</button>
|
||||
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
{this.state.selectedIceCream &&
|
||||
|
||||
<div id="buyForm">
|
||||
<div className={styles.row}>
|
||||
<label className={styles.subTitle}>{this.props.strings.QuantityLabel}: </label>
|
||||
<input type="number" value={this.state.quantity} min="1" onChange={this.quantityChangeHandler.bind(this)} />
|
||||
</div>
|
||||
|
||||
<div className={styles.row}>
|
||||
<button className={styles.button} id="buyButton" onClick={this.buyHandler.bind(this)}>
|
||||
{this.props.strings.BuyLabel} x{this.state.quantity} {this.state.selectedIceCream.Title} {this.props.strings.ForLabel} {this.state.totalPrice}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{this.state.hasBoughtIceCream && <p>{this.props.strings.SuccessLabel}!</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public selectHandler(iceCream: IceCream): void {
|
||||
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
state.selectedIceCream = iceCream;
|
||||
state.totalPrice = Math.round((state.quantity * state.selectedIceCream.Price) * 100) / 100;
|
||||
state.hasBoughtIceCream = false;
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
public buyHandler(): void {
|
||||
if (this.isValid() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uniqueid = this.state.selectedIceCream.UniqueId;
|
||||
const quantity = this.state.quantity;
|
||||
this.props.iceCreamProvider.buy(uniqueid, quantity).then(result => {
|
||||
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
state.hasBoughtIceCream = true;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public quantityChangeHandler(event: React.ChangeEvent<any>) {
|
||||
const inputValue = event.target.value;
|
||||
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
state.quantity = inputValue;
|
||||
state.totalPrice = Math.round((inputValue * state.selectedIceCream.Price) * 100) / 100;
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return this.state.selectedIceCream !== null && this.state.quantity > 0;
|
||||
}
|
||||
}
|
||||
</pre></td></tr>
|
||||
</table></pre>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage
|
||||
generated by <a href="https://istanbul.js.org/" target="_blank">istanbul</a> at Thu May 10 2018 22:15:47 GMT+0100 (GMT Daylight Time)
|
||||
</div>
|
||||
</div>
|
||||
<script src="..\prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
if (typeof prettyPrint === 'function') {
|
||||
prettyPrint();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="..\sorter.js"></script>
|
||||
<script src="..\block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,97 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Code coverage report for components</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="..\prettify.css" />
|
||||
<link rel="stylesheet" href="..\base.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(..\sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>
|
||||
<a href="..\index.html">All files</a> components
|
||||
</h1>
|
||||
<div class='clearfix'>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>40/40</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>12/12</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>15/15</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>36/36</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="IceCreamShop.tsx"><a href="IceCreamShop.tsx.html">IceCreamShop.tsx</a></td>
|
||||
<td data-value="100" class="pic high"><div class="chart"><div class="cover-fill cover-full" style="width: 100%;"></div><div class="cover-empty" style="width:0%;"></div></div></td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="40" class="abs high">40/40</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="12" class="abs high">12/12</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="15" class="abs high">15/15</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="36" class="abs high">36/36</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div><div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage
|
||||
generated by <a href="https://istanbul.js.org/" target="_blank">istanbul</a> at Thu May 10 2018 22:15:47 GMT+0100 (GMT Daylight Time)
|
||||
</div>
|
||||
</div>
|
||||
<script src="..\prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
if (typeof prettyPrint === 'function') {
|
||||
prettyPrint();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="..\sorter.js"></script>
|
||||
<script src="..\block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,141 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Code coverage report for iceCreamProviders/IceCreamFakeProvider.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="..\prettify.css" />
|
||||
<link rel="stylesheet" href="..\base.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(..\sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>
|
||||
<a href="..\index.html">All files</a> / <a href="index.html">iceCreamProviders</a> IceCreamFakeProvider.ts
|
||||
</h1>
|
||||
<div class='clearfix'>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>9/9</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>5/5</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>7/7</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">16x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">5x</span></td><td class="text"><pre class="prettyprint lang-js">import { IceCream } from "./IceCream";
|
||||
import { IIceCreamProvider } from "./IIceCreamProvider";
|
||||
|
||||
export class IceCreamFakeProvider implements IIceCreamProvider {
|
||||
|
||||
public getAll(): Promise<Array<IceCream>> {
|
||||
|
||||
return new Promise<Array<IceCream>>(resolve => {
|
||||
|
||||
let list = [
|
||||
{ UniqueId: "1", Title: "Cherry" },
|
||||
{ UniqueId: "2", Title: "Chocolate" },
|
||||
{ UniqueId: "3", Title: "Coffee and Cookie" },
|
||||
{ UniqueId: "10", Title: "Vanilla" }
|
||||
] as IceCream[];
|
||||
|
||||
resolve(list);
|
||||
});
|
||||
}
|
||||
|
||||
public buy(uniqueid: string, quantity: number): Promise<void> {
|
||||
|
||||
return new Promise<void>(resolve => resolve());
|
||||
}
|
||||
}</pre></td></tr>
|
||||
</table></pre>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage
|
||||
generated by <a href="https://istanbul.js.org/" target="_blank">istanbul</a> at Thu May 10 2018 22:15:47 GMT+0100 (GMT Daylight Time)
|
||||
</div>
|
||||
</div>
|
||||
<script src="..\prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
if (typeof prettyPrint === 'function') {
|
||||
prettyPrint();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="..\sorter.js"></script>
|
||||
<script src="..\block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,204 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Code coverage report for iceCreamProviders/IceCreamPnPJsProvider.ts</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="..\prettify.css" />
|
||||
<link rel="stylesheet" href="..\base.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(..\sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>
|
||||
<a href="..\index.html">All files</a> / <a href="index.html">iceCreamProviders</a> IceCreamPnPJsProvider.ts
|
||||
</h1>
|
||||
<div class='clearfix'>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">90% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>18/20</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">80% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>8/10</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">87.5% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>14/16</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<pre><table class="coverage">
|
||||
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
||||
<a name='L2'></a><a href='#L2'>2</a>
|
||||
<a name='L3'></a><a href='#L3'>3</a>
|
||||
<a name='L4'></a><a href='#L4'>4</a>
|
||||
<a name='L5'></a><a href='#L5'>5</a>
|
||||
<a name='L6'></a><a href='#L6'>6</a>
|
||||
<a name='L7'></a><a href='#L7'>7</a>
|
||||
<a name='L8'></a><a href='#L8'>8</a>
|
||||
<a name='L9'></a><a href='#L9'>9</a>
|
||||
<a name='L10'></a><a href='#L10'>10</a>
|
||||
<a name='L11'></a><a href='#L11'>11</a>
|
||||
<a name='L12'></a><a href='#L12'>12</a>
|
||||
<a name='L13'></a><a href='#L13'>13</a>
|
||||
<a name='L14'></a><a href='#L14'>14</a>
|
||||
<a name='L15'></a><a href='#L15'>15</a>
|
||||
<a name='L16'></a><a href='#L16'>16</a>
|
||||
<a name='L17'></a><a href='#L17'>17</a>
|
||||
<a name='L18'></a><a href='#L18'>18</a>
|
||||
<a name='L19'></a><a href='#L19'>19</a>
|
||||
<a name='L20'></a><a href='#L20'>20</a>
|
||||
<a name='L21'></a><a href='#L21'>21</a>
|
||||
<a name='L22'></a><a href='#L22'>22</a>
|
||||
<a name='L23'></a><a href='#L23'>23</a>
|
||||
<a name='L24'></a><a href='#L24'>24</a>
|
||||
<a name='L25'></a><a href='#L25'>25</a>
|
||||
<a name='L26'></a><a href='#L26'>26</a>
|
||||
<a name='L27'></a><a href='#L27'>27</a>
|
||||
<a name='L28'></a><a href='#L28'>28</a>
|
||||
<a name='L29'></a><a href='#L29'>29</a>
|
||||
<a name='L30'></a><a href='#L30'>30</a>
|
||||
<a name='L31'></a><a href='#L31'>31</a>
|
||||
<a name='L32'></a><a href='#L32'>32</a>
|
||||
<a name='L33'></a><a href='#L33'>33</a>
|
||||
<a name='L34'></a><a href='#L34'>34</a>
|
||||
<a name='L35'></a><a href='#L35'>35</a>
|
||||
<a name='L36'></a><a href='#L36'>36</a>
|
||||
<a name='L37'></a><a href='#L37'>37</a>
|
||||
<a name='L38'></a><a href='#L38'>38</a>
|
||||
<a name='L39'></a><a href='#L39'>39</a>
|
||||
<a name='L40'></a><a href='#L40'>40</a>
|
||||
<a name='L41'></a><a href='#L41'>41</a>
|
||||
<a name='L42'></a><a href='#L42'>42</a>
|
||||
<a name='L43'></a><a href='#L43'>43</a>
|
||||
<a name='L44'></a><a href='#L44'>44</a>
|
||||
<a name='L45'></a><a href='#L45'>45</a>
|
||||
<a name='L46'></a><a href='#L46'>46</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">4x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-yes">3x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">1x</span>
|
||||
<span class="cline-any cline-no"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-neutral"> </span>
|
||||
<span class="cline-any cline-yes">2x</span></td><td class="text"><pre class="prettyprint lang-js">import { IceCream } from "./IceCream";
|
||||
import { IIceCreamProvider } from "./IIceCreamProvider";
|
||||
import { SPRest, SearchQuery, SearchResult, SearchResults } from "@pnp/sp";
|
||||
|
||||
export class IceCreamPnPJsProvider implements IIceCreamProvider {
|
||||
|
||||
private readonly sp: SPRest;
|
||||
|
||||
constructor(sp: SPRest) {
|
||||
this.sp = sp;
|
||||
}
|
||||
|
||||
public getAll(): Promise<IceCream[]> {
|
||||
|
||||
return new Promise<IceCream[]>(async (resolve, reject) => {
|
||||
|
||||
const query: SearchQuery = {
|
||||
RowLimit: 10,
|
||||
SelectProperties: ["UniqueId", "Title", "PriceOWSNMBR"],
|
||||
Querytext: 'path:https://spfxjest.sharepoint.com/sites/jest/Lists/IceCreamFlavours AND contenttypeid:0x01*'
|
||||
} as SearchQuery;
|
||||
|
||||
this.sp.search(query).then((searchResults: SearchResults) => {
|
||||
|
||||
const result = [];
|
||||
for (const item of searchResults.PrimarySearchResults) {
|
||||
result.push({ UniqueId: item.UniqueId, Title: item.Title, Price: Math.round(item["PriceOWSNMBR"] * 100) / 100 });
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}).<span class="fstat-no" title="function not covered" >catch(<span class="cstat-no" title="statement not covered" >error </span>=> reject(error));</span>
|
||||
});
|
||||
}
|
||||
|
||||
public buy(uniqueid: string, quantity: number): Promise<any> {
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.sp.web.lists.getByTitle('Ice Cream Orders').items.add({
|
||||
"Title": uniqueid,
|
||||
"Quantity": quantity
|
||||
})
|
||||
.then(result => resolve())
|
||||
.catch(<span class="fstat-no" title="function not covered" >error<span class="cstat-no" title="statement not covered" > => </span>reject(error));</span>
|
||||
});
|
||||
}
|
||||
}</pre></td></tr>
|
||||
</table></pre>
|
||||
<div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage
|
||||
generated by <a href="https://istanbul.js.org/" target="_blank">istanbul</a> at Thu May 10 2018 22:15:47 GMT+0100 (GMT Daylight Time)
|
||||
</div>
|
||||
</div>
|
||||
<script src="..\prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
if (typeof prettyPrint === 'function') {
|
||||
prettyPrint();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="..\sorter.js"></script>
|
||||
<script src="..\block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,110 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Code coverage report for iceCreamProviders</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="..\prettify.css" />
|
||||
<link rel="stylesheet" href="..\base.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(..\sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>
|
||||
<a href="..\index.html">All files</a> iceCreamProviders
|
||||
</h1>
|
||||
<div class='clearfix'>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">93.1% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>27/29</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>0/0</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">86.67% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>13/15</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">91.3% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>21/23</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="IceCreamFakeProvider.ts"><a href="IceCreamFakeProvider.ts.html">IceCreamFakeProvider.ts</a></td>
|
||||
<td data-value="100" class="pic high"><div class="chart"><div class="cover-fill cover-full" style="width: 100%;"></div><div class="cover-empty" style="width:0%;"></div></div></td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="9" class="abs high">9/9</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="5" class="abs high">5/5</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="7" class="abs high">7/7</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="IceCreamPnPJsProvider.ts"><a href="IceCreamPnPJsProvider.ts.html">IceCreamPnPJsProvider.ts</a></td>
|
||||
<td data-value="90" class="pic high"><div class="chart"><div class="cover-fill" style="width: 90%;"></div><div class="cover-empty" style="width:10%;"></div></div></td>
|
||||
<td data-value="90" class="pct high">90%</td>
|
||||
<td data-value="20" class="abs high">18/20</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="80" class="pct high">80%</td>
|
||||
<td data-value="10" class="abs high">8/10</td>
|
||||
<td data-value="87.5" class="pct high">87.5%</td>
|
||||
<td data-value="16" class="abs high">14/16</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div><div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage
|
||||
generated by <a href="https://istanbul.js.org/" target="_blank">istanbul</a> at Thu May 10 2018 22:15:47 GMT+0100 (GMT Daylight Time)
|
||||
</div>
|
||||
</div>
|
||||
<script src="..\prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
if (typeof prettyPrint === 'function') {
|
||||
prettyPrint();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="..\sorter.js"></script>
|
||||
<script src="..\block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,110 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Code coverage report for All files</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="prettify.css" />
|
||||
<link rel="stylesheet" href="base.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type='text/css'>
|
||||
.coverage-summary .sorter {
|
||||
background-image: url(sort-arrow-sprite.png);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class='wrapper'>
|
||||
<div class='pad1'>
|
||||
<h1>
|
||||
All files
|
||||
</h1>
|
||||
<div class='clearfix'>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">97.1% </span>
|
||||
<span class="quiet">Statements</span>
|
||||
<span class='fraction'>67/69</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">100% </span>
|
||||
<span class="quiet">Branches</span>
|
||||
<span class='fraction'>12/12</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">93.33% </span>
|
||||
<span class="quiet">Functions</span>
|
||||
<span class='fraction'>28/30</span>
|
||||
</div>
|
||||
<div class='fl pad1y space-right2'>
|
||||
<span class="strong">96.61% </span>
|
||||
<span class="quiet">Lines</span>
|
||||
<span class='fraction'>57/59</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="quiet">
|
||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
||||
</p>
|
||||
</div>
|
||||
<div class='status-line high'></div>
|
||||
<div class="pad1">
|
||||
<table class="coverage-summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td class="file high" data-value="components"><a href="components\index.html">components</a></td>
|
||||
<td data-value="100" class="pic high"><div class="chart"><div class="cover-fill cover-full" style="width: 100%;"></div><div class="cover-empty" style="width:0%;"></div></div></td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="40" class="abs high">40/40</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="12" class="abs high">12/12</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="15" class="abs high">15/15</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="36" class="abs high">36/36</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="file high" data-value="iceCreamProviders"><a href="iceCreamProviders\index.html">iceCreamProviders</a></td>
|
||||
<td data-value="93.1" class="pic high"><div class="chart"><div class="cover-fill" style="width: 93%;"></div><div class="cover-empty" style="width:7%;"></div></div></td>
|
||||
<td data-value="93.1" class="pct high">93.1%</td>
|
||||
<td data-value="29" class="abs high">27/29</td>
|
||||
<td data-value="100" class="pct high">100%</td>
|
||||
<td data-value="0" class="abs high">0/0</td>
|
||||
<td data-value="86.67" class="pct high">86.67%</td>
|
||||
<td data-value="15" class="abs high">13/15</td>
|
||||
<td data-value="91.3" class="pct high">91.3%</td>
|
||||
<td data-value="23" class="abs high">21/23</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div><div class='push'></div><!-- for sticky footer -->
|
||||
</div><!-- /wrapper -->
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage
|
||||
generated by <a href="https://istanbul.js.org/" target="_blank">istanbul</a> at Thu May 10 2018 22:15:47 GMT+0100 (GMT Daylight Time)
|
||||
</div>
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
if (typeof prettyPrint === 'function') {
|
||||
prettyPrint();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="sorter.js"></script>
|
||||
<script src="block-navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 209 B |
|
@ -0,0 +1,158 @@
|
|||
var addSorting = (function () {
|
||||
"use strict";
|
||||
var cols,
|
||||
currentSort = {
|
||||
index: 0,
|
||||
desc: false
|
||||
};
|
||||
|
||||
// returns the summary table element
|
||||
function getTable() { return document.querySelector('.coverage-summary'); }
|
||||
// returns the thead element of the summary table
|
||||
function getTableHeader() { return getTable().querySelector('thead tr'); }
|
||||
// returns the tbody element of the summary table
|
||||
function getTableBody() { return getTable().querySelector('tbody'); }
|
||||
// returns the th element for nth column
|
||||
function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
|
||||
|
||||
// loads all columns
|
||||
function loadColumns() {
|
||||
var colNodes = getTableHeader().querySelectorAll('th'),
|
||||
colNode,
|
||||
cols = [],
|
||||
col,
|
||||
i;
|
||||
|
||||
for (i = 0; i < colNodes.length; i += 1) {
|
||||
colNode = colNodes[i];
|
||||
col = {
|
||||
key: colNode.getAttribute('data-col'),
|
||||
sortable: !colNode.getAttribute('data-nosort'),
|
||||
type: colNode.getAttribute('data-type') || 'string'
|
||||
};
|
||||
cols.push(col);
|
||||
if (col.sortable) {
|
||||
col.defaultDescSort = col.type === 'number';
|
||||
colNode.innerHTML = colNode.innerHTML + '<span class="sorter"></span>';
|
||||
}
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
// attaches a data attribute to every tr element with an object
|
||||
// of data values keyed by column name
|
||||
function loadRowData(tableRow) {
|
||||
var tableCols = tableRow.querySelectorAll('td'),
|
||||
colNode,
|
||||
col,
|
||||
data = {},
|
||||
i,
|
||||
val;
|
||||
for (i = 0; i < tableCols.length; i += 1) {
|
||||
colNode = tableCols[i];
|
||||
col = cols[i];
|
||||
val = colNode.getAttribute('data-value');
|
||||
if (col.type === 'number') {
|
||||
val = Number(val);
|
||||
}
|
||||
data[col.key] = val;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
// loads all row data
|
||||
function loadData() {
|
||||
var rows = getTableBody().querySelectorAll('tr'),
|
||||
i;
|
||||
|
||||
for (i = 0; i < rows.length; i += 1) {
|
||||
rows[i].data = loadRowData(rows[i]);
|
||||
}
|
||||
}
|
||||
// sorts the table using the data for the ith column
|
||||
function sortByIndex(index, desc) {
|
||||
var key = cols[index].key,
|
||||
sorter = function (a, b) {
|
||||
a = a.data[key];
|
||||
b = b.data[key];
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
},
|
||||
finalSorter = sorter,
|
||||
tableBody = document.querySelector('.coverage-summary tbody'),
|
||||
rowNodes = tableBody.querySelectorAll('tr'),
|
||||
rows = [],
|
||||
i;
|
||||
|
||||
if (desc) {
|
||||
finalSorter = function (a, b) {
|
||||
return -1 * sorter(a, b);
|
||||
};
|
||||
}
|
||||
|
||||
for (i = 0; i < rowNodes.length; i += 1) {
|
||||
rows.push(rowNodes[i]);
|
||||
tableBody.removeChild(rowNodes[i]);
|
||||
}
|
||||
|
||||
rows.sort(finalSorter);
|
||||
|
||||
for (i = 0; i < rows.length; i += 1) {
|
||||
tableBody.appendChild(rows[i]);
|
||||
}
|
||||
}
|
||||
// removes sort indicators for current column being sorted
|
||||
function removeSortIndicators() {
|
||||
var col = getNthColumn(currentSort.index),
|
||||
cls = col.className;
|
||||
|
||||
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
||||
col.className = cls;
|
||||
}
|
||||
// adds sort indicators for current column being sorted
|
||||
function addSortIndicators() {
|
||||
getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
|
||||
}
|
||||
// adds event listeners for all sorter widgets
|
||||
function enableUI() {
|
||||
var i,
|
||||
el,
|
||||
ithSorter = function ithSorter(i) {
|
||||
var col = cols[i];
|
||||
|
||||
return function () {
|
||||
var desc = col.defaultDescSort;
|
||||
|
||||
if (currentSort.index === i) {
|
||||
desc = !currentSort.desc;
|
||||
}
|
||||
sortByIndex(i, desc);
|
||||
removeSortIndicators();
|
||||
currentSort.index = i;
|
||||
currentSort.desc = desc;
|
||||
addSortIndicators();
|
||||
};
|
||||
};
|
||||
for (i =0 ; i < cols.length; i += 1) {
|
||||
if (cols[i].sortable) {
|
||||
// add the click event handler on the th so users
|
||||
// dont have to click on those tiny arrows
|
||||
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
||||
if (el.addEventListener) {
|
||||
el.addEventListener('click', ithSorter(i));
|
||||
} else {
|
||||
el.attachEvent('onclick', ithSorter(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// adds sorting functionality to the UI
|
||||
return function () {
|
||||
if (!getTable()) {
|
||||
return;
|
||||
}
|
||||
cols = loadColumns();
|
||||
loadData(cols);
|
||||
addSortIndicators();
|
||||
enableUI();
|
||||
};
|
||||
})();
|
||||
|
||||
window.addEventListener('load', addSorting);
|
|
@ -0,0 +1,158 @@
|
|||
TN:
|
||||
SF:C:\Projects\sp-dev-fx-webparts\samples\react-jest-testing\src\webparts\iceCreamShop\components\IceCreamShop.tsx
|
||||
FN:8,(anonymous_5)
|
||||
FN:9,IceCreamShop
|
||||
FN:21,(anonymous_7)
|
||||
FN:23,(anonymous_8)
|
||||
FN:24,(anonymous_9)
|
||||
FN:32,(anonymous_10)
|
||||
FN:42,(anonymous_11)
|
||||
FN:81,(anonymous_12)
|
||||
FN:83,(anonymous_13)
|
||||
FN:91,(anonymous_14)
|
||||
FN:98,(anonymous_15)
|
||||
FN:100,(anonymous_16)
|
||||
FN:107,(anonymous_17)
|
||||
FN:110,(anonymous_18)
|
||||
FN:117,(anonymous_19)
|
||||
FNF:15
|
||||
FNH:15
|
||||
FNDA:6,(anonymous_5)
|
||||
FNDA:18,IceCreamShop
|
||||
FNDA:18,(anonymous_7)
|
||||
FNDA:18,(anonymous_8)
|
||||
FNDA:18,(anonymous_9)
|
||||
FNDA:52,(anonymous_10)
|
||||
FNDA:132,(anonymous_11)
|
||||
FNDA:5,(anonymous_12)
|
||||
FNDA:5,(anonymous_13)
|
||||
FNDA:3,(anonymous_14)
|
||||
FNDA:2,(anonymous_15)
|
||||
FNDA:2,(anonymous_16)
|
||||
FNDA:2,(anonymous_17)
|
||||
FNDA:2,(anonymous_18)
|
||||
FNDA:5,(anonymous_19)
|
||||
DA:1,6
|
||||
DA:2,6
|
||||
DA:8,6
|
||||
DA:9,18
|
||||
DA:21,18
|
||||
DA:23,18
|
||||
DA:24,18
|
||||
DA:26,18
|
||||
DA:27,18
|
||||
DA:32,52
|
||||
DA:33,52
|
||||
DA:44,132
|
||||
DA:81,6
|
||||
DA:83,5
|
||||
DA:84,5
|
||||
DA:85,5
|
||||
DA:86,5
|
||||
DA:87,5
|
||||
DA:91,6
|
||||
DA:92,3
|
||||
DA:93,1
|
||||
DA:96,2
|
||||
DA:97,2
|
||||
DA:98,2
|
||||
DA:100,2
|
||||
DA:101,2
|
||||
DA:102,2
|
||||
DA:107,6
|
||||
DA:108,2
|
||||
DA:110,2
|
||||
DA:111,2
|
||||
DA:112,2
|
||||
DA:113,2
|
||||
DA:117,6
|
||||
DA:118,5
|
||||
DA:120,6
|
||||
LF:36
|
||||
LH:36
|
||||
BRDA:9,0,0,18
|
||||
BRDA:9,0,1,18
|
||||
BRDA:41,1,0,52
|
||||
BRDA:41,1,1,52
|
||||
BRDA:57,2,0,52
|
||||
BRDA:57,2,1,14
|
||||
BRDA:73,3,0,52
|
||||
BRDA:73,3,1,2
|
||||
BRDA:92,4,0,1
|
||||
BRDA:92,4,1,2
|
||||
BRDA:118,5,0,5
|
||||
BRDA:118,5,1,4
|
||||
BRF:12
|
||||
BRH:12
|
||||
end_of_record
|
||||
TN:
|
||||
SF:C:\Projects\sp-dev-fx-webparts\samples\react-jest-testing\src\webparts\iceCreamShop\iceCreamProviders\IceCreamFakeProvider.ts
|
||||
FN:4,(anonymous_0)
|
||||
FN:6,(anonymous_2)
|
||||
FN:8,(anonymous_3)
|
||||
FN:21,(anonymous_4)
|
||||
FN:23,(anonymous_5)
|
||||
FNF:5
|
||||
FNH:5
|
||||
FNDA:21,(anonymous_0)
|
||||
FNDA:16,(anonymous_2)
|
||||
FNDA:16,(anonymous_3)
|
||||
FNDA:1,(anonymous_4)
|
||||
FNDA:1,(anonymous_5)
|
||||
DA:4,5
|
||||
DA:6,5
|
||||
DA:8,16
|
||||
DA:17,16
|
||||
DA:21,5
|
||||
DA:23,1
|
||||
DA:25,5
|
||||
LF:7
|
||||
LH:7
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
||||
TN:
|
||||
SF:C:\Projects\sp-dev-fx-webparts\samples\react-jest-testing\src\webparts\iceCreamShop\iceCreamProviders\IceCreamPnPJsProvider.ts
|
||||
FN:5,(anonymous_12)
|
||||
FN:9,IceCreamPnPJsProvider
|
||||
FN:13,(anonymous_14)
|
||||
FN:15,(anonymous_15)
|
||||
FN:23,(anonymous_18)
|
||||
FN:31,(anonymous_19)
|
||||
FN:35,(anonymous_20)
|
||||
FN:37,(anonymous_21)
|
||||
FN:42,(anonymous_22)
|
||||
FN:43,(anonymous_23)
|
||||
FNF:10
|
||||
FNH:8
|
||||
FNDA:2,(anonymous_12)
|
||||
FNDA:4,IceCreamPnPJsProvider
|
||||
FNDA:1,(anonymous_14)
|
||||
FNDA:1,(anonymous_15)
|
||||
FNDA:1,(anonymous_18)
|
||||
FNDA:0,(anonymous_19)
|
||||
FNDA:1,(anonymous_20)
|
||||
FNDA:1,(anonymous_21)
|
||||
FNDA:1,(anonymous_22)
|
||||
FNDA:0,(anonymous_23)
|
||||
DA:5,2
|
||||
DA:10,4
|
||||
DA:13,2
|
||||
DA:15,1
|
||||
DA:23,1
|
||||
DA:25,1
|
||||
DA:26,3
|
||||
DA:27,3
|
||||
DA:30,1
|
||||
DA:31,0
|
||||
DA:35,2
|
||||
DA:37,1
|
||||
DA:38,1
|
||||
DA:42,1
|
||||
DA:43,0
|
||||
DA:46,2
|
||||
LF:16
|
||||
LH:14
|
||||
BRF:0
|
||||
BRH:0
|
||||
end_of_record
|
|
@ -0,0 +1,56 @@
|
|||
<testsuites name="jest tests" tests="20" failures="0" time="16.183000000000003">
|
||||
<testsuite name="Call the component methods" errors="0" failures="0" skipped="0" timestamp="2018-05-10T21:15:42" time="4.524" tests="7">
|
||||
<testcase classname="Call the component methods should call fail validation if wrong state" name="Call the component methods should call fail validation if wrong state" time="0.02">
|
||||
</testcase>
|
||||
<testcase classname="Call the component methods should call pass validation if wrong state" name="Call the component methods should call pass validation if wrong state" time="0.002">
|
||||
</testcase>
|
||||
<testcase classname="Call the component methods should select the proper item when selectHandler called" name="Call the component methods should select the proper item when selectHandler called" time="0.009">
|
||||
</testcase>
|
||||
<testcase classname="Call the component methods should change the quantity successfully" name="Call the component methods should change the quantity successfully" time="0.004">
|
||||
</testcase>
|
||||
<testcase classname="Call the component methods should re-calculate new price based on quantity change" name="Call the component methods should re-calculate new price based on quantity change" time="0.005">
|
||||
</testcase>
|
||||
<testcase classname="Call the component methods should not show success buyHandler called by the form is not valid" name="Call the component methods should not show success buyHandler called by the form is not valid" time="0.02">
|
||||
</testcase>
|
||||
<testcase classname="Call the component methods should show success message after buy success" name="Call the component methods should show success message after buy success" time="0.015">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
<testsuite name="Stub pnp js to test the provider" errors="0" failures="0" skipped="0" timestamp="2018-05-10T21:15:42" time="4.872" tests="2">
|
||||
<testcase classname="Stub pnp js to test the provider should return array of 3 items when sp.search called" name="Stub pnp js to test the provider should return array of 3 items when sp.search called" time="0.008">
|
||||
</testcase>
|
||||
<testcase classname="Stub pnp js to test the provider should pnp add new item be called" name="Stub pnp js to test the provider should pnp add new item be called" time="0.001">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
<testsuite name="Sinon stubs" errors="0" failures="0" skipped="0" timestamp="2018-05-10T21:15:42" time="4.887" tests="2">
|
||||
<testcase classname="Sinon stubs should show 3 ice cream flavours" name="Sinon stubs should show 3 ice cream flavours" time="0.041">
|
||||
</testcase>
|
||||
<testcase classname="Sinon stubs should show success on successfull buy (e2e unit test)" name="Sinon stubs should show success on successfull buy (e2e unit test)" time="0.038">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
<testsuite name="Enzyme basics" errors="0" failures="0" skipped="0" timestamp="2018-05-10T21:15:47" time="0.319" tests="2">
|
||||
<testcase classname="Enzyme basics should root web part element exists" name="Enzyme basics should root web part element exists" time="0.015">
|
||||
</testcase>
|
||||
<testcase classname="Enzyme basics should has the correct title" name="Enzyme basics should has the correct title" time="0.011">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
<testsuite name="Enzyme props, state, lifecycle events test" errors="0" failures="0" skipped="0" timestamp="2018-05-10T21:15:47" time="0.412" tests="4">
|
||||
<testcase classname="Enzyme props, state, lifecycle events test should has test title is the props" name="Enzyme props, state, lifecycle events test should has test title is the props" time="0.049">
|
||||
</testcase>
|
||||
<testcase classname="Enzyme props, state, lifecycle events test should has initial state" name="Enzyme props, state, lifecycle events test should has initial state" time="0.018">
|
||||
</testcase>
|
||||
<testcase classname="Enzyme props, state, lifecycle events test should buy form be hidden initialy" name="Enzyme props, state, lifecycle events test should buy form be hidden initialy" time="0.012">
|
||||
</testcase>
|
||||
<testcase classname="Enzyme props, state, lifecycle events test should unhide the buy form after ice cream has been selected" name="Enzyme props, state, lifecycle events test should unhide the buy form after ice cream has been selected" time="0.022">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
<testsuite name="Sinon basic spy" errors="0" failures="0" skipped="0" timestamp="2018-05-10T21:15:46" time="0.829" tests="2">
|
||||
<testcase classname="Sinon basic spy should handler be called once" name="Sinon basic spy should handler be called once" time="0.07">
|
||||
</testcase>
|
||||
<testcase classname="Sinon basic spy should handler be called with the right parameters" name="Sinon basic spy should handler be called with the right parameters" time="0.019">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
<testsuite name="Advanced selectors" errors="0" failures="0" skipped="0" timestamp="2018-05-10T21:15:47" time="0.34" tests="1">
|
||||
<testcase classname="Advanced selectors should show a list of ice cream flavours" name="Advanced selectors should show a list of ice cream flavours" time="0.025">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"name": "react-jest-testing",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"@types/react": "15.6.6",
|
||||
"@types/react-dom": "15.5.6",
|
||||
"@microsoft/sp-core-library": "~1.4.1",
|
||||
"@microsoft/sp-webpart-base": "~1.4.1",
|
||||
"@microsoft/sp-lodash-subset": "~1.4.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "~1.4.1",
|
||||
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||
"@pnp/common": "1.0.4",
|
||||
"@pnp/sp": "1.0.4",
|
||||
"@pnp/odata": "1.0.4",
|
||||
"@pnp/logging": "1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~1.4.1",
|
||||
"@microsoft/sp-module-interfaces": "~1.4.1",
|
||||
"@microsoft/sp-webpart-workbench": "~1.4.1",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": ">=3.4.34 <3.6.0",
|
||||
"@types/mocha": ">=2.2.33 <2.6.0",
|
||||
"ajv": "~5.2.2",
|
||||
"@types/jest": "22.2.3",
|
||||
"jest": "22.4.3",
|
||||
"ts-jest": "22.4.5",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"enzyme": "3.3.0",
|
||||
"@types/enzyme": "3.1.10",
|
||||
"enzyme-adapter-react-15": "1.0.5",
|
||||
"react-test-renderer": "15.6.2",
|
||||
"@types/sinon": "4.3.1",
|
||||
"sinon": "5.0.7",
|
||||
"jest-junit": "3.7.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.(ts|tsx)$": "ts-jest"
|
||||
},
|
||||
"testMatch": [
|
||||
"**/src/**/*.test.+(ts|tsx|js)"
|
||||
],
|
||||
"mapCoverage": true,
|
||||
"collectCoverage": true,
|
||||
"coverageReporters": [
|
||||
"json",
|
||||
"lcov",
|
||||
"text",
|
||||
"cobertura"
|
||||
],
|
||||
"coverageDirectory": "<rootDir>/jest",
|
||||
"moduleNameMapper": {
|
||||
"\\.(css|less|scss|sass)$": "identity-obj-proxy"
|
||||
},
|
||||
"testResultsProcessor": "jest-junit",
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 100,
|
||||
"functions": 100,
|
||||
"lines": 100,
|
||||
"statements": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-junit": {
|
||||
"output": "./jest/summary-jest-junit.xml"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "d67c5975-ba3e-49ec-9a23-245c15ea3cda",
|
||||
"alias": "IceCreamShopWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
"requiresCustomScript": false,
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Ice Cream Shop" },
|
||||
"description": { "default": "SPFx unit tests with Jest, Enzyme and Sinonjs" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "Ice Cream Shop"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'IceCreamShopWebPartStrings';
|
||||
import IceCreamShop from './components/IceCreamShop';
|
||||
import { IIceCreamShopProps } from './components/IIceCreamShopProps';
|
||||
// import { IceCreamFakeProvider } from './iceCreamProviders/IceCreamFakeProvider'; // when offline workbench.
|
||||
|
||||
import { sp } from "@pnp/sp";
|
||||
import { IceCreamPnPJsProvider } from './iceCreamProviders/IceCreamPnPJsProvider';
|
||||
|
||||
export interface IIceCreamShopWebPartProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class IceCreamShopWebPart extends BaseClientSideWebPart<IIceCreamShopWebPartProps> {
|
||||
|
||||
public onInit(): Promise<void> {
|
||||
|
||||
return super.onInit().then(_ => {
|
||||
sp.setup({
|
||||
spfxContext: this.context
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IIceCreamShopProps> = React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamPnPJsProvider(sp), //new IceCreamFakeProvider() // when offline workbench.
|
||||
strings: strings
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { IIceCreamProvider } from "../iceCreamProviders/IIceCreamProvider";
|
||||
|
||||
export interface IIceCreamShopProps {
|
||||
iceCreamProvider: IIceCreamProvider;
|
||||
strings: IIceCreamShopWebPartStrings;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { IceCream } from "../iceCreamProviders/IceCream";
|
||||
|
||||
export interface IIceCreamShopState {
|
||||
selectedIceCream: IceCream;
|
||||
quantity: number;
|
||||
iceCreamFlavoursList: IceCream[];
|
||||
totalPrice: number;
|
||||
hasBoughtIceCream: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
|
||||
|
||||
.ic {
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include ms-font-l;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: $ms-color-themePrimary;
|
||||
border-color: $ms-color-themePrimary;
|
||||
color: $ms-color-white;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: $ms-font-size-m;
|
||||
font-weight: $ms-font-weight-regular;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: $ms-font-weight-semibold;
|
||||
font-size: $ms-font-size-m;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
import * as React from 'react';
|
||||
import styles from './IceCreamShop.module.scss';
|
||||
import { IIceCreamShopProps } from './IIceCreamShopProps';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
import { IIceCreamShopState } from './IIceCreamShopState';
|
||||
import { IceCream } from '../iceCreamProviders/IceCream';
|
||||
|
||||
export default class IceCreamShop extends React.Component<IIceCreamShopProps, IIceCreamShopState> {
|
||||
constructor(props: IIceCreamShopProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
iceCreamFlavoursList: [],
|
||||
quantity: 1,
|
||||
selectedIceCream: null,
|
||||
totalPrice: 0,
|
||||
hasBoughtIceCream: false
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
|
||||
this.props.iceCreamProvider.getAll().then((result: IceCream[]) => {
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
|
||||
state.iceCreamFlavoursList = result;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IIceCreamShopProps> {
|
||||
return (
|
||||
<div className={styles.ic} id="iceCreamShop">
|
||||
<div className={styles.container}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.column}>
|
||||
<h1 className={styles.title}>{this.props.strings.TitleLabel}</h1>
|
||||
<div id="iceCreamFlavoursList">
|
||||
{
|
||||
this.state.iceCreamFlavoursList &&
|
||||
this.state.iceCreamFlavoursList.map((item, index) => {
|
||||
|
||||
return <div key={index}>
|
||||
|
||||
<div className={styles.subTitle}>{item.Title}</div>
|
||||
|
||||
<button data-automationid={`item-${index}`} className={styles.button} onClick={this.selectHandler.bind(this, item)}>
|
||||
{this.props.strings.GetItLabel} {item.Price}
|
||||
</button>
|
||||
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
{this.state.selectedIceCream &&
|
||||
|
||||
<div id="buyForm">
|
||||
<div className={styles.row}>
|
||||
<label className={styles.subTitle}>{this.props.strings.QuantityLabel}: </label>
|
||||
<input type="number" value={this.state.quantity} min="1" onChange={this.quantityChangeHandler.bind(this)} />
|
||||
</div>
|
||||
|
||||
<div className={styles.row}>
|
||||
<button className={styles.button} id="buyButton" onClick={this.buyHandler.bind(this)}>
|
||||
{this.props.strings.BuyLabel} x{this.state.quantity} {this.state.selectedIceCream.Title} {this.props.strings.ForLabel} {this.state.totalPrice}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{this.state.hasBoughtIceCream && <p>{this.props.strings.SuccessLabel}!</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public selectHandler(iceCream: IceCream): void {
|
||||
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
state.selectedIceCream = iceCream;
|
||||
state.totalPrice = Math.round((state.quantity * state.selectedIceCream.Price) * 100) / 100;
|
||||
state.hasBoughtIceCream = false;
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
public buyHandler(): void {
|
||||
if (this.isValid() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uniqueid = this.state.selectedIceCream.UniqueId;
|
||||
const quantity = this.state.quantity;
|
||||
this.props.iceCreamProvider.buy(uniqueid, quantity).then(result => {
|
||||
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
state.hasBoughtIceCream = true;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public quantityChangeHandler(event: React.ChangeEvent<any>) {
|
||||
const inputValue = event.target.value;
|
||||
|
||||
this.setState((state: IIceCreamShopState): IIceCreamShopState => {
|
||||
state.quantity = inputValue;
|
||||
state.totalPrice = Math.round((inputValue * state.selectedIceCream.Price) * 100) / 100;
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return this.state.selectedIceCream !== null && this.state.quantity > 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { IceCream } from "./IceCream";
|
||||
|
||||
export interface IIceCreamProvider {
|
||||
|
||||
getAll(): Promise<IceCream[]>;
|
||||
|
||||
buy(uniqueid: string, quantity: number): Promise<void>;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface IceCream {
|
||||
UniqueId: string;
|
||||
Title: string;
|
||||
Price: number;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { IceCream } from "./IceCream";
|
||||
import { IIceCreamProvider } from "./IIceCreamProvider";
|
||||
|
||||
export class IceCreamFakeProvider implements IIceCreamProvider {
|
||||
|
||||
public getAll(): Promise<Array<IceCream>> {
|
||||
|
||||
return new Promise<Array<IceCream>>(resolve => {
|
||||
|
||||
let list = [
|
||||
{ UniqueId: "1", Title: "Cherry" },
|
||||
{ UniqueId: "2", Title: "Chocolate" },
|
||||
{ UniqueId: "3", Title: "Coffee and Cookie" },
|
||||
{ UniqueId: "10", Title: "Vanilla" }
|
||||
] as IceCream[];
|
||||
|
||||
resolve(list);
|
||||
});
|
||||
}
|
||||
|
||||
public buy(uniqueid: string, quantity: number): Promise<void> {
|
||||
|
||||
return new Promise<void>(resolve => resolve());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { IceCream } from "./IceCream";
|
||||
import { IIceCreamProvider } from "./IIceCreamProvider";
|
||||
import { SPRest, SearchQuery, SearchResult, SearchResults } from "@pnp/sp";
|
||||
|
||||
export class IceCreamPnPJsProvider implements IIceCreamProvider {
|
||||
|
||||
private readonly sp: SPRest;
|
||||
|
||||
constructor(sp: SPRest) {
|
||||
this.sp = sp;
|
||||
}
|
||||
|
||||
public getAll(): Promise<IceCream[]> {
|
||||
|
||||
return new Promise<IceCream[]>(async (resolve, reject) => {
|
||||
|
||||
const query: SearchQuery = {
|
||||
RowLimit: 10,
|
||||
SelectProperties: ["UniqueId", "Title", "PriceOWSNMBR"],
|
||||
Querytext: 'path:https://spfxjest.sharepoint.com/sites/jest/Lists/IceCreamFlavours AND contenttypeid:0x01*'
|
||||
} as SearchQuery;
|
||||
|
||||
this.sp.search(query).then((searchResults: SearchResults) => {
|
||||
|
||||
const result = [];
|
||||
for (const item of searchResults.PrimarySearchResults) {
|
||||
result.push({ UniqueId: item.UniqueId, Title: item.Title, Price: Math.round(item["PriceOWSNMBR"] * 100) / 100 });
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
public buy(uniqueid: string, quantity: number): Promise<any> {
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.sp.web.lists.getByTitle('Ice Cream Orders').items.add({
|
||||
"Title": uniqueid,
|
||||
"Quantity": quantity
|
||||
})
|
||||
.then(result => resolve())
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field",
|
||||
"GetItLabel": "Get it for just ",
|
||||
"QuantityLabel": "Quantity",
|
||||
"ForLabel": "for",
|
||||
"BuyLabel": "Buy",
|
||||
"SuccessLabel": "Success",
|
||||
"TitleLabel": "PnP Ice Cream Shop"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
declare interface IIceCreamShopWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
TitleLabel: string;
|
||||
GetItLabel: string;
|
||||
QuantityLabel: string;
|
||||
ForLabel: string;
|
||||
BuyLabel: string;
|
||||
SuccessLabel: string;
|
||||
}
|
||||
|
||||
declare module 'IceCreamShopWebPartStrings' {
|
||||
const strings: IIceCreamShopWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/// <reference types="jest" />
|
||||
|
||||
import * as React from 'react';
|
||||
import { configure, mount, ReactWrapper } from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
import IceCreamShop from '../components/IceCreamShop';
|
||||
import { IceCreamFakeProvider } from '../iceCreamProviders/IceCreamFakeProvider';
|
||||
import { IIceCreamShopProps } from '../components/IIceCreamShopProps';
|
||||
import { IIceCreamShopState } from '../components/IIceCreamShopState';
|
||||
|
||||
describe('Enzyme basics', () => {
|
||||
|
||||
let reactComponent: ReactWrapper<IIceCreamShopProps, IIceCreamShopState>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
reactComponent = mount(React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamFakeProvider(),
|
||||
strings: {
|
||||
TitleLabel: "PnP Ice Cream Shop"
|
||||
} as IIceCreamShopWebPartStrings
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
reactComponent.unmount();
|
||||
});
|
||||
|
||||
it('should root web part element exists', () => {
|
||||
|
||||
// define the css selector
|
||||
let cssSelector: string = '#iceCreamShop';
|
||||
|
||||
// find the element using css selector
|
||||
const element = reactComponent.find(cssSelector);
|
||||
expect(element.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should has the correct title', () => {
|
||||
|
||||
// define contains/like css selector
|
||||
let cssSelector: string = 'h1';
|
||||
|
||||
// find the elemet using css selector
|
||||
const text = reactComponent.find(cssSelector).text();
|
||||
|
||||
expect(text).toBe("PnP Ice Cream Shop");
|
||||
});
|
||||
});
|
||||
|
||||
// Usefull links:
|
||||
// https://reactjs.org/docs/test-renderer.html
|
||||
// https://github.com/airbnb/enzyme
|
|
@ -0,0 +1,72 @@
|
|||
/// <reference types="jest" />
|
||||
|
||||
import * as React from 'react';
|
||||
import { configure, mount, ReactWrapper} from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
import { IIceCreamShopProps } from '../components/IIceCreamShopProps';
|
||||
import { IIceCreamShopState } from '../components/IIceCreamShopState';
|
||||
import IceCreamShop from '../components/IceCreamShop';
|
||||
import { IceCreamFakeProvider } from '../iceCreamProviders/IceCreamFakeProvider';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
describe('Enzyme props, state, lifecycle events test', () => {
|
||||
|
||||
let reactComponent: ReactWrapper<IIceCreamShopProps, IIceCreamShopState>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
reactComponent = mount(React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamFakeProvider(),
|
||||
strings: {
|
||||
TitleLabel: "PnP Ice Cream Shop"
|
||||
} as IIceCreamShopWebPartStrings
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
reactComponent.unmount();
|
||||
});
|
||||
|
||||
it('should has test title is the props', () => {
|
||||
|
||||
expect(reactComponent.props().strings.TitleLabel).toBe("PnP Ice Cream Shop");
|
||||
});
|
||||
|
||||
it('should has initial state', () => {
|
||||
|
||||
const state = reactComponent.state();
|
||||
|
||||
expect(state.hasBoughtIceCream).toBe(false);
|
||||
expect(state.quantity).toBe(1);
|
||||
expect(state.selectedIceCream).toBe(null);
|
||||
});
|
||||
|
||||
it('should buy form be hidden initialy', () => {
|
||||
|
||||
const buyForm = reactComponent.find("#buyForm");
|
||||
|
||||
expect(buyForm.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should unhide the buy form after ice cream has been selected', () => {
|
||||
|
||||
reactComponent.update(); // http://airbnb.io/enzyme/docs/api/ShallowWrapper/update.html
|
||||
|
||||
// more advanced selector
|
||||
const selectIceCreamButton = reactComponent.find("#iceCreamFlavoursList button").first();
|
||||
|
||||
selectIceCreamButton.simulate('click');
|
||||
|
||||
// after the selectIceCreamButton is clicked
|
||||
// the buy form should be rendered
|
||||
// lets try to find in in the component
|
||||
const buyForm = reactComponent.find("#buyForm");
|
||||
|
||||
expect(buyForm.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
/// <reference types="jest" />
|
||||
|
||||
import * as React from 'react';
|
||||
import { configure, mount, ReactWrapper} from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
import { IIceCreamShopProps } from '../components/IIceCreamShopProps';
|
||||
import { IIceCreamShopState } from '../components/IIceCreamShopState';
|
||||
import IceCreamShop from '../components/IceCreamShop';
|
||||
import { IceCreamFakeProvider } from '../iceCreamProviders/IceCreamFakeProvider';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
describe('Advanced selectors', () => {
|
||||
|
||||
let reactComponent: ReactWrapper<IIceCreamShopProps, IIceCreamShopState>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
reactComponent = mount(React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamFakeProvider(),
|
||||
strings: {} as IIceCreamShopWebPartStrings
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
reactComponent.unmount();
|
||||
});
|
||||
|
||||
it('should show a list of ice cream flavours', () => {
|
||||
|
||||
reactComponent.update();
|
||||
|
||||
// get the component as dom
|
||||
const componentAsDOM = reactComponent.getDOMNode();
|
||||
|
||||
// use JavaScript querySelectorAll to find nodes that contain text
|
||||
const flavours = componentAsDOM.querySelectorAll("[data-automationid*='item-']");
|
||||
|
||||
expect(flavours.length).toBe(4);
|
||||
});
|
||||
});
|
||||
// Usefull links:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Document_object_model/Locating_DOM_elements_using_selectors
|
|
@ -0,0 +1,130 @@
|
|||
/// <reference types="jest" />
|
||||
|
||||
import * as React from 'react';
|
||||
import { configure, shallow, ShallowWrapper } from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
import { IIceCreamShopProps } from '../components/IIceCreamShopProps';
|
||||
import { IIceCreamShopState } from '../components/IIceCreamShopState';
|
||||
import IceCreamShop from '../components/IceCreamShop';
|
||||
import { IceCreamFakeProvider } from '../iceCreamProviders/IceCreamFakeProvider';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
describe('Call the component methods', () => {
|
||||
|
||||
let reactComponent: ShallowWrapper<IIceCreamShopProps, IIceCreamShopState>;
|
||||
|
||||
beforeEach(() => {
|
||||
reactComponent = shallow(React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamFakeProvider(),
|
||||
strings: {} as IIceCreamShopWebPartStrings
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
reactComponent.unmount();
|
||||
});
|
||||
|
||||
it('should call fail validation if wrong state', () => {
|
||||
|
||||
// set state
|
||||
reactComponent.setState({ selectedIceCream: null, quantity: 1 });
|
||||
|
||||
// get the wrapper instance
|
||||
const instance = reactComponent.instance() as IceCreamShop;
|
||||
const isValid = instance.isValid();
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('should call pass validation if wrong state', () => {
|
||||
|
||||
// set state
|
||||
reactComponent.setState({ selectedIceCream: { UniqueId: "123", Title: "abc", Price: 13 }, quantity: 1 });
|
||||
|
||||
// get the wrapper instance
|
||||
const instance = reactComponent.instance() as IceCreamShop;
|
||||
const isValid = instance.isValid();
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should select the proper item when selectHandler called', () => {
|
||||
|
||||
// set state
|
||||
reactComponent.setState({ selectedIceCream: null, quantity: 1 });
|
||||
|
||||
// get the wrapper instance
|
||||
const instance = reactComponent.instance() as IceCreamShop;
|
||||
instance.selectHandler({ UniqueId: "123", Title: "abc", Price: 13 });
|
||||
|
||||
expect(reactComponent.state().selectedIceCream.UniqueId).toBe("123");
|
||||
expect(reactComponent.state().selectedIceCream.Title).toBe("abc");
|
||||
expect(reactComponent.state().selectedIceCream.Price).toBe(13);
|
||||
});
|
||||
|
||||
it('should change the quantity successfully', () => {
|
||||
|
||||
// set state
|
||||
reactComponent.setState({ selectedIceCream: { UniqueId: "123", Title: "abc", Price: 2 }, quantity: 1 });
|
||||
|
||||
// get the wrapper instance
|
||||
const instance = reactComponent.instance() as IceCreamShop;
|
||||
instance.quantityChangeHandler({ target: { value: 22 } } as React.ChangeEvent<any>);
|
||||
|
||||
expect(reactComponent.state().quantity).toBe(22);
|
||||
});
|
||||
|
||||
it('should re-calculate new price based on quantity change', () => {
|
||||
|
||||
// set state
|
||||
reactComponent.setState({ selectedIceCream: { UniqueId: "123", Title: "abc", Price: 2 }, quantity: 1 });
|
||||
|
||||
// get the wrapper instance
|
||||
const instance = reactComponent.instance() as IceCreamShop;
|
||||
instance.quantityChangeHandler({ target: { value: 10 } } as React.ChangeEvent<any>);
|
||||
|
||||
expect(reactComponent.state().totalPrice).toBe(10 * 2); // quantity = 10, price = 2
|
||||
});
|
||||
|
||||
it('should not show success buyHandler called by the form is not valid', (done) => {
|
||||
|
||||
// set state
|
||||
reactComponent.setState({ selectedIceCream: { UniqueId: "123", Title: "abc", Price: 2 }, quantity: -1 });
|
||||
|
||||
// get the wrapper instance
|
||||
const instance = reactComponent.instance() as IceCreamShop;
|
||||
instance.buyHandler();
|
||||
|
||||
// since the buyHandler is a void method,
|
||||
// we do not know when the promise inside will resolve
|
||||
// this is why we have to add timeout and expect to be
|
||||
// resolved after the timeout
|
||||
setTimeout(() => {
|
||||
expect(reactComponent.state().hasBoughtIceCream).toBe(false);
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
it('should show success message after buy success', (done) => {
|
||||
|
||||
// set state
|
||||
reactComponent.setState({ selectedIceCream: { UniqueId: "123", Title: "abc", Price: 2 }, quantity: 1 });
|
||||
|
||||
// get the wrapper instance
|
||||
const instance = reactComponent.instance() as IceCreamShop;
|
||||
instance.buyHandler();
|
||||
|
||||
// since the buyHandler is a void method,
|
||||
// we do not know when the promise inside will resolve
|
||||
// this is why we have to add timeout and expect to be
|
||||
// resolved after the timeout
|
||||
setTimeout(() => {
|
||||
|
||||
expect(reactComponent.state().hasBoughtIceCream).toBe(true);
|
||||
done();
|
||||
}, 10);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
/// <reference types="jest" />
|
||||
|
||||
import * as React from 'react';
|
||||
import { configure, mount, ReactWrapper } from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
import { IIceCreamShopProps } from '../components/IIceCreamShopProps';
|
||||
import { IIceCreamShopState } from '../components/IIceCreamShopState';
|
||||
import IceCreamShop from '../components/IceCreamShop';
|
||||
import { IceCreamFakeProvider } from '../iceCreamProviders/IceCreamFakeProvider';
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
describe('Sinon basic spy', () => {
|
||||
|
||||
let reactComponent: ReactWrapper<IIceCreamShopProps, IIceCreamShopState>;
|
||||
let selectHandlerSpy: sinon.SinonSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
// set spy on the select handler
|
||||
selectHandlerSpy = sinon.spy(IceCreamShop.prototype, 'selectHandler');
|
||||
|
||||
// mount the component
|
||||
reactComponent = mount(React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamFakeProvider(),
|
||||
strings: {} as IIceCreamShopWebPartStrings
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
reactComponent.unmount();
|
||||
selectHandlerSpy.restore();
|
||||
});
|
||||
|
||||
it('should handler be called once', () => {
|
||||
|
||||
reactComponent.update();
|
||||
|
||||
// more advanced selector
|
||||
const selectIceCreamButton = reactComponent.find("#iceCreamFlavoursList button").first();
|
||||
|
||||
selectIceCreamButton.simulate('click');
|
||||
|
||||
// after the selectIceCreamButton is clicked
|
||||
// the buy form should be rendered
|
||||
// lets try to find in in the component
|
||||
const buyForm = reactComponent.find("#buyForm");
|
||||
|
||||
expect(selectHandlerSpy.calledOnce).toBe(true);
|
||||
});
|
||||
|
||||
it('should handler be called with the right parameters', () => {
|
||||
|
||||
reactComponent.update();
|
||||
|
||||
// more advanced selector
|
||||
const selectIceCreamButton = reactComponent.find("#iceCreamFlavoursList button").first();
|
||||
|
||||
selectIceCreamButton.simulate('click');
|
||||
|
||||
// after the selectIceCreamButton is clicked
|
||||
// the buy form should be rendered
|
||||
// lets try to find in in the component
|
||||
const buyForm = reactComponent.find("#buyForm");
|
||||
|
||||
expect(selectHandlerSpy.calledWith({ UniqueId: "1", Title: "Cherry" })).toBe(true);
|
||||
});
|
||||
});
|
||||
// http://sinonjs.org/
|
|
@ -0,0 +1,133 @@
|
|||
/// <reference types="jest" />
|
||||
|
||||
import * as React from 'react';
|
||||
import { configure, mount, ReactWrapper } from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
import { IIceCreamShopProps } from '../components/IIceCreamShopProps';
|
||||
import { IIceCreamShopState } from '../components/IIceCreamShopState';
|
||||
import IceCreamShop from '../components/IceCreamShop';
|
||||
import { IceCreamPnPJsProvider } from '../iceCreamProviders/IceCreamPnPJsProvider';
|
||||
import { IceCream } from '../iceCreamProviders/IceCream';
|
||||
import { sp } from "@pnp/sp";
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
describe('Sinon stubs', () => {
|
||||
|
||||
let reactComponent: ReactWrapper<IIceCreamShopProps, IIceCreamShopState>;
|
||||
let iceCreamPnPJsProviderGetAllStub: sinon.SinonStub;
|
||||
let iceCreamPnPJsProviderBuyStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
// set stubs on the pnp js provider so it does not call SharePoint at all
|
||||
iceCreamPnPJsProviderGetAllStub = sinon.stub(IceCreamPnPJsProvider.prototype, "getAll");
|
||||
iceCreamPnPJsProviderBuyStub = sinon.stub(IceCreamPnPJsProvider.prototype, "buy");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
reactComponent.unmount();
|
||||
iceCreamPnPJsProviderGetAllStub.restore();
|
||||
iceCreamPnPJsProviderBuyStub.restore();
|
||||
});
|
||||
|
||||
it('should show 3 ice cream flavours', (done) => {
|
||||
|
||||
// mocks the promise and resolves it returnig
|
||||
// 3 items, no https call is made.
|
||||
iceCreamPnPJsProviderGetAllStub.resolves([
|
||||
{ UniqueId: "GUID1", Title: "Cherry", Price: 1 },
|
||||
{ UniqueId: "GUID2", Title: "Chocolate", Price: 2 },
|
||||
{ UniqueId: "GUID3", Title: "Coffee and Cookie", Price: 3 }
|
||||
] as IceCream[]);
|
||||
|
||||
// mount the component
|
||||
// componentDidMount will be called
|
||||
// and the component should recieve the data above
|
||||
reactComponent = mount(React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamPnPJsProvider(sp),
|
||||
strings: {} as IIceCreamShopWebPartStrings
|
||||
}
|
||||
));
|
||||
|
||||
// since the componentDidMount is a lifecycle event,
|
||||
// we do not know when the promise inside will resolve
|
||||
// this is why we have to add timeout and expect to be
|
||||
// resolved after the timeout
|
||||
setTimeout(() => {
|
||||
// check if the state is populated with the data
|
||||
const items = reactComponent.state().iceCreamFlavoursList;
|
||||
|
||||
expect(items.length).toBe(3);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should show success on successfull buy (e2e unit test)', (done) => {
|
||||
|
||||
// mocks the promise and resolves it returnig
|
||||
// 3 items, no https call is made.
|
||||
iceCreamPnPJsProviderGetAllStub.resolves([
|
||||
{ UniqueId: "GUID1", Title: "Cherry", Price: 1 },
|
||||
{ UniqueId: "GUID2", Title: "Chocolate", Price: 2 },
|
||||
{ UniqueId: "GUID3", Title: "Coffee and Cookie", Price: 3 }
|
||||
] as IceCream[]);
|
||||
|
||||
// mocks buy success ignoring any calls
|
||||
// over https
|
||||
iceCreamPnPJsProviderBuyStub.resolves();
|
||||
|
||||
// mount the component
|
||||
reactComponent = mount(React.createElement(
|
||||
IceCreamShop,
|
||||
{
|
||||
iceCreamProvider: new IceCreamPnPJsProvider(sp),
|
||||
strings: {
|
||||
TitleLabel: "PnP Ice Cream Shop"
|
||||
} as IIceCreamShopWebPartStrings
|
||||
}
|
||||
));
|
||||
|
||||
// we have to wait componenDidMount to load
|
||||
// the fake data
|
||||
setTimeout(() => {
|
||||
reactComponent.update();
|
||||
|
||||
// find first ice cream flavour and select it
|
||||
const selectIceCreamButton = reactComponent.find("#iceCreamFlavoursList button").first();
|
||||
selectIceCreamButton.simulate('click');
|
||||
|
||||
reactComponent.update();
|
||||
|
||||
// find the buy button and buy it
|
||||
const buyButton = reactComponent.find("#buyButton").first();
|
||||
|
||||
buyButton.simulate('click');
|
||||
|
||||
// since the buyHandler is a void method,
|
||||
// we do not know when the promise inside will resolve
|
||||
// this is why we have to add timeout and expect to be
|
||||
// resolved after the timeout
|
||||
setTimeout(() => {
|
||||
// check if the state is populated with the data
|
||||
const items = reactComponent.state().iceCreamFlavoursList;
|
||||
|
||||
expect(reactComponent.state().hasBoughtIceCream).toBe(true);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
// http://sinonjs.org/
|
||||
|
||||
// Remarks: In general the last test has two timeouts due void handlers
|
||||
// of the react component handlers. It has to wait the first time for
|
||||
// componentDidMount to load the ice creams list, but then has to wait
|
||||
// with setTimeout for the buyHandler to complete because it is void.
|
||||
/// If the buyHandler was promise, then this would remove
|
||||
// the need of a second timeout function.
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/// <reference types="jest" />
|
||||
|
||||
import * as React from 'react';
|
||||
import { configure, mount, ReactWrapper } from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-15';
|
||||
|
||||
import { IceCreamPnPJsProvider } from '../iceCreamProviders/IceCreamPnPJsProvider';
|
||||
import { IceCream } from '../iceCreamProviders/IceCream';
|
||||
import { sp, SearchResults, Items } from "@pnp/sp";
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
describe('Stub pnp js to test the provider', () => {
|
||||
|
||||
let myPnPJsProvider: IceCreamPnPJsProvider;
|
||||
let pnpSearchStub: sinon.SinonStub;
|
||||
let pnpItemsAddStub: sinon.SinonStub;
|
||||
let pnpListGetByTitleStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
// set stubs on the pnp js mathods
|
||||
pnpSearchStub = sinon.stub(sp, "search");
|
||||
pnpListGetByTitleStub = sinon.stub(sp.web.lists, "getByTitle");
|
||||
pnpItemsAddStub = sinon.stub(Items.prototype, "add");
|
||||
|
||||
// create instance of the pnp js provider to test it
|
||||
myPnPJsProvider = new IceCreamPnPJsProvider(sp);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
pnpSearchStub.restore();
|
||||
pnpListGetByTitleStub.restore();
|
||||
pnpItemsAddStub.restore();
|
||||
});
|
||||
|
||||
it('should return array of 3 items when sp.search called', (done) => {
|
||||
|
||||
// mocks the search api so it returns exactly whan we
|
||||
// want and no https calls are made
|
||||
pnpSearchStub.resolves({
|
||||
PrimarySearchResults: [
|
||||
{ UniqueId: "GUID1", Title: "Cherry", PriceOWSNMBR: 1 },
|
||||
{ UniqueId: "GUID2", Title: "Chocolate", PriceOWSNMBR: 2 },
|
||||
{ UniqueId: "GUID3", Title: "Coffee and Cookie", PriceOWSNMBR: 3 }
|
||||
]
|
||||
});
|
||||
|
||||
// call the stub and see if the provider works as expected
|
||||
myPnPJsProvider.getAll()
|
||||
.then((result: IceCream[]) => {
|
||||
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
done();
|
||||
}).catch(e => {
|
||||
|
||||
done.fail(new Error('Filed to retrieve data.'));
|
||||
// done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pnp add new item be called', (done) => {
|
||||
|
||||
// mocks the sp.list.getByTitle api so it resolves with success
|
||||
pnpListGetByTitleStub.resolves();
|
||||
// mocks the sp...items.add api so it resolves with success
|
||||
pnpItemsAddStub.resolves();
|
||||
|
||||
// test my pnp provider now
|
||||
myPnPJsProvider.buy("123", 1)
|
||||
.then(result => {
|
||||
|
||||
expect(result).toBe(undefined); // since buy is void method
|
||||
done();
|
||||
}).catch(e => {
|
||||
|
||||
done.fail(new Error('Filed to retrieve data.'));
|
||||
// done();
|
||||
});
|
||||
});
|
||||
});
|
||||
// https://facebook.github.io/jest/docs/en/tutorial-async.html
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue