From 518b818cb7997ce446ee1af04914ed3a97b933b9 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 25 May 2020 13:31:09 +0200 Subject: [PATCH 01/23] Started adding plot with timed sweep of epsilon --- README.md | 180 +++++++++++++++++++++++++++- src/js/example-app.html | 3 + src/js/example-jsonschema-form.html | 3 + 3 files changed, 185 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1838f37..c86c25e 100644 --- a/README.md +++ b/README.md @@ -952,7 +952,6 @@ To make writing a SPA easier, a number of frameworks have been developed. The mo They have their strengths and weaknesses which are summarized in the [here](https://en.wikipedia.org/wiki/Comparison_of_JavaScript_frameworks#Features). - For Newton-Raphson web application I picked React as it is light and functional, because I like the small API footprint and the functional programming paradigm. The C++ algorithm is compiled into a wasm file using bindings. When a calculation form is submitted in the React application a web worker loads the wasm file, starts the calculation, renders the result. With this architecture the application only needs cheap static file hosting to host the html, js and wasm files. **The calculation will be done in the web browser on the end users machine instead of a server**. @@ -1319,3 +1318,182 @@ If you enter a negative number in the `epsilon` field the form will be invalid a ### Visualization The plots in web apllicatoin can be made using [vega-lite](https://vega.github.io/vega-lite/). Vega-lite is a JS library which accepts a JSON document describing the plot and generates interactive graphics. + +To make an interesting plot we need more than one result. We are going to do a parameter sweep and measure how long each calculation takes. + +Let us make a new JSON schema in which we can set a max, min and step for epsilon. + +```{.js #plot-app} +// this JavaScript snippet is later referred to as <> +const schema = { + "type": "object", + "properties": { + "epsilon": { + "title": "Epsilon", + "type": "object", + "properties": { + "min": { + "type": "number", + "minimum": 0, + "default": 0.0001 + }, + "max": { + "type": "number", + "minimum": 0, + "default": 0.001 + }, + "step": { + "type": "number", + "minimum": 0, + "default": 0.0001 + } + }, + "required": ["min", "max", "step"], + "additionalProperties": false + }, + "guess": { + "title": "Initial guess", + "type": "number", + "default": -20 + } + }, + "required": ["epsilon", "guess"], + "additionalProperties": false +} +``` + +```{.js #plot-app} +const Form = JSONSchemaForm.default; +``` + +```{.js file=src/js/worker-sweep.js} +// this JavaScript snippet stored as src/js/worker-sweep.js +importScripts('newtonraphsonwasm.js'); + +onmessage = function(message) { + if (message.data.type === 'CALCULATE') { + createModule().then((module) => { + const {min,max,step} = message.data.payload.epsilon; + const guess = message.data.payload.guess; + const roots = []; + for (let epsilon = min; epsilon <= max; epsilon += step) { + const t0 = performance.now(); + const finder = new module.NewtonRaphson(epsilon); + const root = finder.find(guess); + const t1 = performance.now(); + roots.push({ + epsilon, + guess, + root, + duration: t1 - t0 + }); + } + postMessage({ + type: 'RESULT', + payload: { + roots + } + }); + }); + } +}; +``` + +```{.js #plot-app} +// this JavaScript snippet is appended to <> +const [roots, setRoots] = React.useState([]); + +function handleSubmit({formData}, event) { + event.preventDefault(); + const worker = new Worker('worker-sweep.js'); + worker.postMessage({ + type: 'CALCULATE', + payload: { epsilon: formData.epsilon, guess: formData.guess } + }); + worker.onmessage = function(message) { + if (message.data.type === 'RESULT') { + const result = message.data.payload.roots; + setRoots(result); + worker.terminate(); + } + }; +} +``` + +```{.html #imports} + + + +``` + +```{.jsx #plot-component} +// this JavaScript snippet is later referred to as <> + +function Plot({data}) { + const container = React.useRef(null); + React.useEffect(() => { + if (container === null || data.length === 0) { + return; + } + console.log(data); + const spec = { + "$schema": "https://vega.github.io/schema/vega-lite/v4.json", + "description": "A scatterplot showing of root finding with different epsilons versus the calculation duration", + "data": {"values": data}, + "mark": "point", + "encoding": { + "x": {"field": "epsilon", "type": "quantitative"}, + "y": {"field": "duration", "type": "quantitative"} + }, + "width": 800, + "height": 600 + }; + vegaEmbed(container.current, spec); + }, [container, data]); + + return
; +} +``` + +The App component can be defined and rendered with. + +```{.jsx file=src/js/plot-app.js} +// this JavaScript snippet stored as src/js/plot-app.js +<> + +<> + +function App() { + <> + + return ( +
+ + <> + +
+ ); +} + +ReactDOM.render( + , + document.getElementById('container') +); +``` + +The `Heading` and `Result` React component can be reused. + +```{.jsx file=src/js/plot-app.js} +// this JavaScript snippet appended to src/js/plot-app.js +``` + +```{.html file=src/js/example-plot.html} + + + + <> +
+ + + +``` diff --git a/src/js/example-app.html b/src/js/example-app.html index fab26d9..1b5240a 100644 --- a/src/js/example-app.html +++ b/src/js/example-app.html @@ -10,6 +10,9 @@ + + +
diff --git a/src/js/example-jsonschema-form.html b/src/js/example-jsonschema-form.html index d3fc452..5343591 100644 --- a/src/js/example-jsonschema-form.html +++ b/src/js/example-jsonschema-form.html @@ -10,6 +10,9 @@ + + +
From 0eeb66bf5c71cb59b840986a3460d47189639aff Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 25 May 2020 14:54:19 +0200 Subject: [PATCH 02/23] Use a controlled form --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index eadbf36..37a8e18 100644 --- a/README.md +++ b/README.md @@ -1384,6 +1384,8 @@ const schema = { "guess": { "title": "Initial guess", "type": "number", + "minimum": -100, + "maximum": 100, "default": -20 } }, @@ -1394,6 +1396,16 @@ const schema = { ```{.js #plot-app} const Form = JSONSchemaForm.default; +const uiSchema = { + "guess": { + "ui:widget": "range" + } +} +const [formData, setFormData] = React.useState({}); + +function handleChange(event) { + setFormData(event.formData); +} ``` ```{.js file=src/js/worker-sweep.js} From b6837e60de105c66b2d742aab1e0b449d5b30e30 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 25 May 2020 18:30:46 +0200 Subject: [PATCH 03/23] Cast string to number + better tests --- README.md | 6 +- TESTING.md | 20 ++- cypress/integration/example-app_spec.js | 4 +- .../example-jsonschema-form_spec.js | 4 +- cypress/integration/example-plot_spec.js | 7 + src/js/app.js | 4 +- src/js/example-plot.html | 19 +++ src/js/plot-app.js | 124 ++++++++++++++++++ src/js/worker-sweep.js | 30 +++++ 9 files changed, 205 insertions(+), 13 deletions(-) create mode 100644 cypress/integration/example-plot_spec.js create mode 100644 src/js/example-plot.html create mode 100644 src/js/plot-app.js create mode 100644 src/js/worker-sweep.js diff --git a/README.md b/README.md index d05f309..ddad9e0 100644 --- a/README.md +++ b/README.md @@ -1067,12 +1067,12 @@ const [epsilon, setEpsilon] = React.useState(0.001); The argument of the `useState` function is the initial value. The `epsilon` variable contains the current value for epsilon and `setEpsilon` is a function to set epsilon to a new value. -The input tag in the form will call the `onChange` function with a event object. We need to extract the user input from the event and pass it to `setEpsilon`. +The input tag in the form will call the `onChange` function with a event object. We need to extract the user input from the event and pass it to `setEpsilon`. The value should be a number, so we use `*1` to cast the string from the event to a number. ```{.js #react-state} // this JavaScript snippet is appended to <> function onEpsilonChange(event) { - setEpsilon(event.target.value); + setEpsilon(event.target.value*1); } ``` @@ -1083,7 +1083,7 @@ We will follow the same steps for the guess input as well. const [guess, setGuess] = React.useState(-20); function onGuessChange(event) { - setGuess(event.target.value); + setGuess(event.target.value*1); } ``` diff --git a/TESTING.md b/TESTING.md index 21fb37d..d262d93 100644 --- a/TESTING.md +++ b/TESTING.md @@ -38,8 +38,8 @@ Let us also change the guess value. describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - cy.get('input[name=guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[name=guess]').type('0'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); @@ -52,14 +52,26 @@ And another test for the full application, but now with JSON schema powered form describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - cy.get('input[id=root_guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[id=root_epsilon]').type('{selectall}0.1'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); }); ``` +And lastly a test for the web application with a plot. + +```{.js file=cypress/integration/example-plot_spec.js} +describe('src/js/example-plot.html', () => { + it('should render -1.00', () => { + cy.visit('http://localhost:8000/src/js/example-plot.html'); + cy.contains('Submit').click(); + // TODO assert plot + }); +}); +``` + The tests can be run with ```{.awk #test-wasm} diff --git a/cypress/integration/example-app_spec.js b/cypress/integration/example-app_spec.js index f084140..65ea69e 100644 --- a/cypress/integration/example-app_spec.js +++ b/cypress/integration/example-app_spec.js @@ -1,8 +1,8 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - cy.get('input[name=guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[name=guess]').type('0'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-jsonschema-form_spec.js b/cypress/integration/example-jsonschema-form_spec.js index 7069561..ead379d 100644 --- a/cypress/integration/example-jsonschema-form_spec.js +++ b/cypress/integration/example-jsonschema-form_spec.js @@ -1,8 +1,8 @@ describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - cy.get('input[id=root_guess]').type('-30'); - cy.get('input[id=root_guess]').contains('-30'); + cy.get('input[id=root_epsilon]').type('{selectall}0.1'); + // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-plot_spec.js b/cypress/integration/example-plot_spec.js new file mode 100644 index 0000000..5941127 --- /dev/null +++ b/cypress/integration/example-plot_spec.js @@ -0,0 +1,7 @@ +describe('src/js/example-plot.html', () => { + it('should render -1.00', () => { + cy.visit('http://localhost:8000/src/js/example-plot.html'); + cy.contains('Submit').click(); + // TODO assert plot + }); +}); \ No newline at end of file diff --git a/src/js/app.js b/src/js/app.js index a405e78..cfcbbc1 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -19,13 +19,13 @@ function App() { const [epsilon, setEpsilon] = React.useState(0.001); // this JavaScript snippet is appended to <> function onEpsilonChange(event) { - setEpsilon(event.target.value); + setEpsilon(event.target.value*1); } // this JavaScript snippet is appended to <> const [guess, setGuess] = React.useState(-20); function onGuessChange(event) { - setGuess(event.target.value); + setGuess(event.target.value*1); } // this JavaScript snippet is appended to <> const [root, setRoot] = React.useState(undefined); diff --git a/src/js/example-plot.html b/src/js/example-plot.html new file mode 100644 index 0000000..39a80d5 --- /dev/null +++ b/src/js/example-plot.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/src/js/plot-app.js b/src/js/plot-app.js new file mode 100644 index 0000000..deef570 --- /dev/null +++ b/src/js/plot-app.js @@ -0,0 +1,124 @@ +// this JavaScript snippet stored as src/js/plot-app.js +// this JavaScript snippet is later referred to as <> +function Heading() { + const title = 'Root finding web application'; + return

{title}

+} + +// this JavaScript snippet is later referred to as <> + +function Plot({data}) { + const container = React.useRef(null); + React.useEffect(() => { + if (container === null || data.length === 0) { + return; + } + console.log(data); + const spec = { + "$schema": "https://vega.github.io/schema/vega-lite/v4.json", + "description": "A scatterplot showing of root finding with different epsilons versus the calculation duration", + "data": {"values": data}, + "mark": "point", + "encoding": { + "x": {"field": "epsilon", "type": "quantitative"}, + "y": {"field": "duration", "type": "quantitative"} + }, + "width": 800, + "height": 600 + }; + vegaEmbed(container.current, spec); + }, [container, data]); + + return
; +} + +function App() { + // this JavaScript snippet is later referred to as <> + const schema = { + "type": "object", + "properties": { + "epsilon": { + "title": "Epsilon", + "type": "object", + "properties": { + "min": { + "type": "number", + "minimum": 0, + "default": 0.0001 + }, + "max": { + "type": "number", + "minimum": 0, + "default": 0.001 + }, + "step": { + "type": "number", + "minimum": 0, + "default": 0.0001 + } + }, + "required": ["min", "max", "step"], + "additionalProperties": false + }, + "guess": { + "title": "Initial guess", + "type": "number", + "minimum": -100, + "maximum": 100, + "default": -20 + } + }, + "required": ["epsilon", "guess"], + "additionalProperties": false + } + const Form = JSONSchemaForm.default; + const uiSchema = { + "guess": { + "ui:widget": "range" + } + } + const [formData, setFormData] = React.useState({}); + + function handleChange(event) { + setFormData(event.formData); + } + // this JavaScript snippet is appended to <> + const [roots, setRoots] = React.useState([]); + + function handleSubmit({formData}, event) { + event.preventDefault(); + const worker = new Worker('worker-sweep.js'); + worker.postMessage({ + type: 'CALCULATE', + payload: { epsilon: formData.epsilon, guess: formData.guess } + }); + worker.onmessage = function(message) { + if (message.data.type === 'RESULT') { + const result = message.data.payload.roots; + setRoots(result); + worker.terminate(); + } + }; + } + + return ( +
+ + { /* this JavaScript snippet is later referred to as <> */} +
+ +
+ ); +} + +ReactDOM.render( + , + document.getElementById('container') +); +// this JavaScript snippet appended to src/js/plot-app.js \ No newline at end of file diff --git a/src/js/worker-sweep.js b/src/js/worker-sweep.js new file mode 100644 index 0000000..20ab648 --- /dev/null +++ b/src/js/worker-sweep.js @@ -0,0 +1,30 @@ +// this JavaScript snippet stored as src/js/worker-sweep.js +importScripts('newtonraphsonwasm.js'); + +onmessage = function(message) { + if (message.data.type === 'CALCULATE') { + createModule().then((module) => { + const {min,max,step} = message.data.payload.epsilon; + const guess = message.data.payload.guess; + const roots = []; + for (let epsilon = min; epsilon <= max; epsilon += step) { + const t0 = performance.now(); + const finder = new module.NewtonRaphson(epsilon); + const root = finder.find(guess); + const t1 = performance.now(); + roots.push({ + epsilon, + guess, + root, + duration: t1 - t0 + }); + } + postMessage({ + type: 'RESULT', + payload: { + roots + } + }); + }); + } +}; \ No newline at end of file From e762d0b3c081e617e584603d72e5211214733ddb Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Tue, 26 May 2020 10:42:17 +0200 Subject: [PATCH 04/23] Explain why we create Form alias --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ddad9e0..9841bf2 100644 --- a/README.md +++ b/README.md @@ -1250,7 +1250,7 @@ The form by default uses the [Bootstrap 3](https://getbootstrap.com/docs/3.4/) t ``` -The form component is exported as `JSONSchemaForm.default` and can be aliases with +The form component is exported as `JSONSchemaForm.default` for easy use we alias it to `Form` with ```{.js #jsonschema-app} // this JavaScript snippet is appended to <> From e06a006adc624b15c7997b3fba5ea0f688bc8fbb Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Tue, 26 May 2020 11:05:00 +0200 Subject: [PATCH 05/23] Write story around visualization code snippets --- README.md | 201 +++++++++++++++++++++++++++++------------ src/js/plot-app.js | 53 ++++++----- src/js/worker-sweep.js | 12 ++- 3 files changed, 181 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 9841bf2..e357761 100644 --- a/README.md +++ b/README.md @@ -1359,11 +1359,11 @@ If you enter a negative number in the `epsilon` field the form will be invalid a ### Visualization -The plots in web apllicatoin can be made using [vega-lite](https://vega.github.io/vega-lite/). Vega-lite is a JS library which accepts a JSON document describing the plot and generates interactive graphics. +The plots in web application can be made using [vega-lite](https://vega.github.io/vega-lite/). Vega-lite is a JS library which accepts a JSON document describing the plot. To make an interesting plot we need more than one result. We are going to do a parameter sweep and measure how long each calculation takes. -Let us make a new JSON schema in which we can set a max, min and step for epsilon. +Lets make a new JSON schema for the form in which we can set a max, min and step for epsilon. ```{.js #plot-app} // this JavaScript snippet is later referred to as <> @@ -1406,20 +1406,91 @@ const schema = { } ``` -```{.js #plot-app} -const Form = JSONSchemaForm.default; -const uiSchema = { - "guess": { - "ui:widget": "range" - } +We need to rewrite the worker to perform a parameter sweep. +The worker will recieve a payload like + +```json +{ + "epsilon": { + "min": 0.0001, + "max": 0.001, + "step": 0.0001 + }, + "guess": -20 } -const [formData, setFormData] = React.useState({}); +``` -function handleChange(event) { - setFormData(event.formData); +The worker will send back an array containing objects with the root result, the input parameters and the duration in milliseconds. + +```json +[{ + "epsilon": 0.0001, + "guess": -20, + "root": -1, + "duration": 0.61 +}] +``` + +To perform the sweep we will first unpack the payload. + +```{.js #calculate-sweep} +// this JavaScript snippet is later referred to as <> +const {min, max, step} = message.data.payload.epsilon; +const guess = message.data.payload.guess; +``` + +The result array needs to be initialized. + +```{.js #calculate-sweep} +// this JavaScript snippet appended to <> +const roots = []; +``` + +Lets use a [classic for loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for) to iterate over requested the epsilons. + +```{.js #calculate-sweep} +// this JavaScript snippet appended to <> +for (let epsilon = min; epsilon <= max; epsilon += step) { +``` + +To measure the duration of a calculation we use the [performance.now()](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now) method which returns a timestamp in milliseconds. + +```{.js #calculate-sweep} + // this JavaScript snippet appended to <> + const t0 = performance.now(); + const finder = new module.NewtonRaphson(epsilon); + const root = finder.find(guess); + const duration = performance.now() - t0; +``` + +We append the root result object using [shorthand property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) to the result array. + +```{.js #calculate-sweep} + // this JavaScript snippet appended to <> + roots.push({ + epsilon, + guess, + root, + duration + }); +``` + +To complete the sweep calculation we need to close the for loop and post the result. + +```{.js #calculate-sweep} + // this JavaScript snippet appended to <> } +postMessage({ + type: 'RESULT', + payload: { + roots + } +}); ``` +The sweep calculation snippet (`<>`) must be run in a new web worker called `worker-sweep.js`. +Like before we need to wait for the WebAssembly module to be initialized before we can start the calculation. + ```{.js file=src/js/worker-sweep.js} // this JavaScript snippet stored as src/js/worker-sweep.js importScripts('newtonraphsonwasm.js'); @@ -1427,32 +1498,14 @@ importScripts('newtonraphsonwasm.js'); onmessage = function(message) { if (message.data.type === 'CALCULATE') { createModule().then((module) => { - const {min,max,step} = message.data.payload.epsilon; - const guess = message.data.payload.guess; - const roots = []; - for (let epsilon = min; epsilon <= max; epsilon += step) { - const t0 = performance.now(); - const finder = new module.NewtonRaphson(epsilon); - const root = finder.find(guess); - const t1 = performance.now(); - roots.push({ - epsilon, - guess, - root, - duration: t1 - t0 - }); - } - postMessage({ - type: 'RESULT', - payload: { - roots - } - }); + <> }); } }; ``` +To handle the submit we will start a worker, send the form data to the worker, recieve the workers result and store it in the `roots` variable. + ```{.js #plot-app} // this JavaScript snippet is appended to <> const [roots, setRoots] = React.useState([]); @@ -1462,7 +1515,7 @@ function handleSubmit({formData}, event) { const worker = new Worker('worker-sweep.js'); worker.postMessage({ type: 'CALCULATE', - payload: { epsilon: formData.epsilon, guess: formData.guess } + payload: formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { @@ -1474,36 +1527,50 @@ function handleSubmit({formData}, event) { } ``` +Now that we got data, we are ready to plot. We use the + [Vega-Lite specification](https://vega.github.io/vega-lite/docs/spec.html) to declare the plot. +The specification for a scatter plot of the `epsilon` against the `duration` looks like. + +```{.js #vega-lite-spec} +// this JavaScript snippet is later referred to as <> +const spec = { + "$schema": "https://vega.github.io/schema/vega-lite/v4.json", + "data": { "values": roots }, + "mark": "point", + "encoding": { + "x": { "field": "epsilon", "type": "quantitative" }, + "y": { "field": "duration", "type": "quantitative", "title": "Duration (ms)" } + }, + "width": 800, + "height": 600 +}; +``` + +To render the spec we use the [vegaEmbed](https://github.com/vega/vega-embed) module. The Vega-Lite specification is a simplification of the [Vega specification](https://vega.github.io/vega/docs/specification/) so wil first import `vega` then `vega-lite` and lastly `vega-embed`. + ```{.html #imports} ``` +The `vegaEmbed()` function needs a DOM element to render the plot in. +In React we must use the [useRef](https://reactjs.org/docs/hooks-reference.html#useref) hook to get a reference to a DOM element. As the DOM element needs time to initialize we need to use the [useEffect](https://reactjs.org/docs/hooks-effect.html) hook to only embed the plot when the DOM element is ready. The `Plot` React component can be written as + ```{.jsx #plot-component} // this JavaScript snippet is later referred to as <> - -function Plot({data}) { +function Plot({roots}) { const container = React.useRef(null); - React.useEffect(() => { - if (container === null || data.length === 0) { + + function didUpdate() { + if (container.current === null) { return; } - console.log(data); - const spec = { - "$schema": "https://vega.github.io/schema/vega-lite/v4.json", - "description": "A scatterplot showing of root finding with different epsilons versus the calculation duration", - "data": {"values": data}, - "mark": "point", - "encoding": { - "x": {"field": "epsilon", "type": "quantitative"}, - "y": {"field": "duration", "type": "quantitative"} - }, - "width": 800, - "height": 600 - }; + <> vegaEmbed(container.current, spec); - }, [container, data]); + } + const dependencies = [container, roots]; + React.useEffect(didUpdate, dependencies); return
; } @@ -1518,13 +1585,27 @@ The App component can be defined and rendered with. <> function App() { + const Form = JSONSchemaForm.default; + const uiSchema = { + "guess": { + "ui:widget": "range" + } + } + const [formData, setFormData] = React.useState({ + + }); + + function handleChange(event) { + setFormData(event.formData); + } + <> return (
<> - +
); } @@ -1535,11 +1616,7 @@ ReactDOM.render( ); ``` -The `Heading` and `Result` React component can be reused. - -```{.jsx file=src/js/plot-app.js} -// this JavaScript snippet appended to src/js/plot-app.js -``` +The html page should look like ```{.html file=src/js/example-plot.html} @@ -1551,3 +1628,13 @@ The `Heading` and `Result` React component can be reused. ``` + +Like before we also need to host the files in a web server with + +```shell +python3 -m http.server 8000 +``` + +Visit [http://localhost:8000/src/js/example-plot.html](http://localhost:8000/src/js/example-plot.html) to see the epislon/duration plot. + +After the submit button is pressed the plot should show that the first calculation took a bit longer then the rest. diff --git a/src/js/plot-app.js b/src/js/plot-app.js index deef570..18ae571 100644 --- a/src/js/plot-app.js +++ b/src/js/plot-app.js @@ -6,33 +6,48 @@ function Heading() { } // this JavaScript snippet is later referred to as <> - -function Plot({data}) { +function Plot({roots}) { const container = React.useRef(null); - React.useEffect(() => { - if (container === null || data.length === 0) { + + function didUpdate() { + if (container.current === null) { return; } - console.log(data); + // this JavaScript snippet is later referred to as <> const spec = { "$schema": "https://vega.github.io/schema/vega-lite/v4.json", - "description": "A scatterplot showing of root finding with different epsilons versus the calculation duration", - "data": {"values": data}, + "data": { "values": roots }, "mark": "point", "encoding": { - "x": {"field": "epsilon", "type": "quantitative"}, - "y": {"field": "duration", "type": "quantitative"} + "x": { "field": "epsilon", "type": "quantitative" }, + "y": { "field": "duration", "type": "quantitative", "title": "Duration (ms)" } }, "width": 800, "height": 600 }; vegaEmbed(container.current, spec); - }, [container, data]); + } + const dependencies = [container, roots]; + React.useEffect(didUpdate, dependencies); return
; } function App() { + const Form = JSONSchemaForm.default; + const uiSchema = { + "guess": { + "ui:widget": "range" + } + } + const [formData, setFormData] = React.useState({ + + }); + + function handleChange(event) { + setFormData(event.formData); + } + // this JavaScript snippet is later referred to as <> const schema = { "type": "object", @@ -71,17 +86,6 @@ function App() { "required": ["epsilon", "guess"], "additionalProperties": false } - const Form = JSONSchemaForm.default; - const uiSchema = { - "guess": { - "ui:widget": "range" - } - } - const [formData, setFormData] = React.useState({}); - - function handleChange(event) { - setFormData(event.formData); - } // this JavaScript snippet is appended to <> const [roots, setRoots] = React.useState([]); @@ -90,7 +94,7 @@ function App() { const worker = new Worker('worker-sweep.js'); worker.postMessage({ type: 'CALCULATE', - payload: { epsilon: formData.epsilon, guess: formData.guess } + payload: formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { @@ -112,7 +116,7 @@ function App() { onChange={handleChange} onSubmit={handleSubmit} /> - +
); } @@ -120,5 +124,4 @@ function App() { ReactDOM.render( , document.getElementById('container') -); -// this JavaScript snippet appended to src/js/plot-app.js \ No newline at end of file +); \ No newline at end of file diff --git a/src/js/worker-sweep.js b/src/js/worker-sweep.js index 20ab648..808205f 100644 --- a/src/js/worker-sweep.js +++ b/src/js/worker-sweep.js @@ -4,20 +4,26 @@ importScripts('newtonraphsonwasm.js'); onmessage = function(message) { if (message.data.type === 'CALCULATE') { createModule().then((module) => { - const {min,max,step} = message.data.payload.epsilon; + // this JavaScript snippet is later referred to as <> + const {min, max, step} = message.data.payload.epsilon; const guess = message.data.payload.guess; + // this JavaScript snippet appended to <> const roots = []; + // this JavaScript snippet appended to <> for (let epsilon = min; epsilon <= max; epsilon += step) { + // this JavaScript snippet appended to <> const t0 = performance.now(); const finder = new module.NewtonRaphson(epsilon); const root = finder.find(guess); - const t1 = performance.now(); + const duration = performance.now() - t0; + // this JavaScript snippet appended to <> roots.push({ epsilon, guess, root, - duration: t1 - t0 + duration }); + // this JavaScript snippet appended to <> } postMessage({ type: 'RESULT', From 02338b624ecd9f0c5fc0360159aea3a4301eb480 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Tue, 2 Jun 2020 12:36:39 +0200 Subject: [PATCH 06/23] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6eca8be..2233c22 100644 --- a/README.md +++ b/README.md @@ -1635,6 +1635,6 @@ Like before we also need to host the files in a web server with python3 -m http.server 8000 ``` -Visit [http://localhost:8000/src/js/example-plot.html](http://localhost:8000/src/js/example-plot.html) to see the epislon/duration plot. +Visit [http://localhost:8000/src/js/example-plot.html](http://localhost:8000/src/js/example-plot.html) to see the epsilon/duration plot. After the submit button is pressed the plot should show that the first calculation took a bit longer then the rest. From 88a8e16877d37dd55e56cb63334661dee5d15d2a Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Thu, 4 Jun 2020 17:55:19 +0200 Subject: [PATCH 07/23] Embed vega plot --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 23a0cd8..0a8c719 100644 --- a/README.md +++ b/README.md @@ -1591,4 +1591,8 @@ python3 -m http.server 8000 Visit [http://localhost:8000/src/js/example-plot.html](http://localhost:8000/src/js/example-plot.html) to see the epsilon/duration plot. +Embedded below is the example app hosted on [GitHub pages](https://nlesc-jcer.github.io/cpp2wasm/src/js/example-plot.html) + + + After the submit button is pressed the plot should show that the first calculation took a bit longer then the rest. From 96a54d9a476718b05444cf822a297fbcc18b4caf Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Thu, 4 Jun 2020 18:03:23 +0200 Subject: [PATCH 08/23] Remove unneeded comments + ref #55 in plot assertion --- TESTING.md | 4 +--- cypress/integration/example-app_spec.js | 1 - cypress/integration/example-jsonschema-form_spec.js | 1 - cypress/integration/example-plot_spec.js | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/TESTING.md b/TESTING.md index bc8b7a4..2527ad0 100644 --- a/TESTING.md +++ b/TESTING.md @@ -39,7 +39,6 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); cy.get('input[name=guess]').type('0'); - // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); @@ -53,7 +52,6 @@ describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); cy.get('input[id=root_epsilon]').type('{selectall}0.1'); - // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); @@ -67,7 +65,7 @@ describe('src/js/example-plot.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-plot.html'); cy.contains('Submit').click(); - // TODO assert plot + // TODO assert plot has been plotted, see https://github.com/NLESC-JCER/cpp2wasm/issues/55 }); }); ``` diff --git a/cypress/integration/example-app_spec.js b/cypress/integration/example-app_spec.js index 65ea69e..cdc566e 100644 --- a/cypress/integration/example-app_spec.js +++ b/cypress/integration/example-app_spec.js @@ -2,7 +2,6 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); cy.get('input[name=guess]').type('0'); - // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-jsonschema-form_spec.js b/cypress/integration/example-jsonschema-form_spec.js index ead379d..9d0afd2 100644 --- a/cypress/integration/example-jsonschema-form_spec.js +++ b/cypress/integration/example-jsonschema-form_spec.js @@ -2,7 +2,6 @@ describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); cy.get('input[id=root_epsilon]').type('{selectall}0.1'); - // TODO assert value is set cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-plot_spec.js b/cypress/integration/example-plot_spec.js index 5941127..384d166 100644 --- a/cypress/integration/example-plot_spec.js +++ b/cypress/integration/example-plot_spec.js @@ -2,6 +2,6 @@ describe('src/js/example-plot.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-plot.html'); cy.contains('Submit').click(); - // TODO assert plot + // TODO assert plot has been plotted, see https://github.com/NLESC-JCER/cpp2wasm/issues/55 }); }); \ No newline at end of file From e4d9de578d56e65469cf0a3d4046964bd29706b3 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Thu, 4 Jun 2020 18:09:34 +0200 Subject: [PATCH 09/23] Upgraded vega + vega-lite to latest version --- README.md | 4 ++-- src/js/example-app.html | 4 ++-- src/js/example-jsonschema-form.html | 4 ++-- src/js/example-plot.html | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0a8c719..9326870 100644 --- a/README.md +++ b/README.md @@ -1503,8 +1503,8 @@ const spec = { To render the spec we use the [vegaEmbed](https://github.com/vega/vega-embed) module. The Vega-Lite specification is a simplification of the [Vega specification](https://vega.github.io/vega/docs/specification/) so wil first import `vega` then `vega-lite` and lastly `vega-embed`. ```{.html #imports} - - + + ``` diff --git a/src/js/example-app.html b/src/js/example-app.html index 1b5240a..79cfa00 100644 --- a/src/js/example-app.html +++ b/src/js/example-app.html @@ -10,8 +10,8 @@ - - + +
diff --git a/src/js/example-jsonschema-form.html b/src/js/example-jsonschema-form.html index 5343591..12e39e2 100644 --- a/src/js/example-jsonschema-form.html +++ b/src/js/example-jsonschema-form.html @@ -10,8 +10,8 @@ - - + +
diff --git a/src/js/example-plot.html b/src/js/example-plot.html index 39a80d5..fdac982 100644 --- a/src/js/example-plot.html +++ b/src/js/example-plot.html @@ -10,8 +10,8 @@ - - + +
From 01301d3327d2f01b7c9d5b270c5b0ccf4de67f1a Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Fri, 5 Jun 2020 12:13:01 +0200 Subject: [PATCH 10/23] Add comments to tests --- TESTING.md | 18 ++++++++++++++---- cypress/integration/example-app_spec.js | 1 + .../example-jsonschema-form_spec.js | 5 ++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/TESTING.md b/TESTING.md index 2527ad0..b61c97f 100644 --- a/TESTING.md +++ b/TESTING.md @@ -2,12 +2,18 @@ To make sure [JavaScript and WebAssembly code snippets](README.md#JavaScript) and [Single page application](README.md#single-page-application) work we want have a tests for them. -To test, we will use the [cypress](https://www.cypress.io/) JavaScript end to end testing framework. +To test, we will use the [cypress](https://www.cypress.io/) JavaScript end to end testing framework. Cypress can simulate user behavior such as clicking buttons etc. and checks expected result in a web browser. -In the following example, we test if the example web page renders the answer `-1.00` when it is visited. +In the following examples, we test if the example web pages render the answer `-1.00` when they are visited. -Let's, first write a test for the direct WebAssembly example. +To visit a web page we need to start a web server with + +```shell +python3 -m http.server 8000 +``` + +Let's, first write a test for the [direct WebAssembly example](http://localhost:8000/src/js/example.html). ```{.js file=cypress/integration/example_spec.js} // this JavaScript snippet is run by cypress and is stored as cypress/integration/example_spec.js @@ -38,6 +44,7 @@ Let us also change the guess value. describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); + // In initial guess input field type 0 to append 0 to default -20 so the value is -200 cy.get('input[name=guess]').type('0'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); @@ -51,7 +58,10 @@ And another test for the full application, but now with JSON schema powered form describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - cy.get('input[id=root_epsilon]').type('{selectall}0.1'); + // The JSON schema powered form uses a hierarchy of identifers for each input field starting with `root`, as the `epsilon` input field is a direct child of root it has `root_epsilon` as an identifier + const input_selector = 'input[id=root_epsilon]'; + // In initial guess input field replace default value of initial guess with 0.1 + cy.get(input_selector).type('{selectall}0.1'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); diff --git a/cypress/integration/example-app_spec.js b/cypress/integration/example-app_spec.js index cdc566e..fc8f47c 100644 --- a/cypress/integration/example-app_spec.js +++ b/cypress/integration/example-app_spec.js @@ -1,6 +1,7 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); + // In initial guess input field type 0 to append 0 to default -20 so the value is -200 cy.get('input[name=guess]').type('0'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); diff --git a/cypress/integration/example-jsonschema-form_spec.js b/cypress/integration/example-jsonschema-form_spec.js index 9d0afd2..82d4935 100644 --- a/cypress/integration/example-jsonschema-form_spec.js +++ b/cypress/integration/example-jsonschema-form_spec.js @@ -1,7 +1,10 @@ describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - cy.get('input[id=root_epsilon]').type('{selectall}0.1'); + // The JSON schema powered form uses a hierarchy of identifers for each input field starting with `root`, as the `epsilon` input field is a direct child of root it has `root_epsilon` as an identifier + const input_selector = 'input[id=root_epsilon]'; + // In initial guess input field replace default value of initial guess with 0.1 + cy.get(input_selector).type('{selectall}0.1'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); }); From 9f82596e7eb222b18fdfd9cac3640ee04940a53e Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Fri, 5 Jun 2020 12:16:40 +0200 Subject: [PATCH 11/23] Linkup tests --- TESTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TESTING.md b/TESTING.md index b61c97f..a698d82 100644 --- a/TESTING.md +++ b/TESTING.md @@ -25,7 +25,7 @@ describe('src/js/example.html', () => { }); ``` -Second, a test for the WebAssembly called through a web worker. +Second, a test for the WebAssembly called through a [web worker](http://localhost:8000/src/js/example-web-worker.html). ```{.js file=cypress/integration/example-web-worker_spec.js} // this JavaScript snippet is run by cypress and is stored as cypress/integration/example-web-worker_spec.js @@ -37,8 +37,8 @@ describe('src/js/example-web-worker.html', () => { }); ``` -And lastly, a test for the React/form/Web worker/WebAssembly combination. -Let us also change the guess value. +Third, a test for the [React/form/Web worker/WebAssembly combination](http://localhost:8000/src/js/example-app.html). +Let us also change the initial guess value. ```{.js file=cypress/integration/example-app_spec.js} describe('src/js/example-app.html', () => { @@ -52,7 +52,7 @@ describe('src/js/example-app.html', () => { }); ``` -And another test for the full application, but now with JSON schema powered form. +And another test for the full application, but now with [JSON schema powered form](http://localhost:8000/src/js/example-jsonschema-form.html). ```{.js file=cypress/integration/example-jsonschema-form_spec.js} describe('src/js/example-jsonschema-form.html', () => { @@ -68,7 +68,7 @@ describe('src/js/example-jsonschema-form.html', () => { }); ``` -And lastly a test for the web application with a plot. +And lastly a test for the [web application with a plot](http://localhost:8000/src/js/example-plot.html). ```{.js file=cypress/integration/example-plot_spec.js} describe('src/js/example-plot.html', () => { From e54e3c30b99da8cf5330ee9f2f577a80adb132f2 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Fri, 5 Jun 2020 13:49:30 +0200 Subject: [PATCH 12/23] Fix sonarcloud issues --- README.md | 4 +++- src/js/example-plot.html | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6170d27..f5d5633 100644 --- a/README.md +++ b/README.md @@ -1573,7 +1573,9 @@ The html page should look like ```{.html file=src/js/example-plot.html} - + + Example plot + <>
diff --git a/src/js/example-plot.html b/src/js/example-plot.html index fdac982..086a0f0 100644 --- a/src/js/example-plot.html +++ b/src/js/example-plot.html @@ -1,6 +1,8 @@ - + + Example plot + From aa2b6160b0809bbb3b1bc4402ed64d4e0f043d0d Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Fri, 5 Jun 2020 13:51:41 +0200 Subject: [PATCH 13/23] Add proper title --- README.md | 15 +++++++++------ src/js/example-plot.html | 35 +++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f5d5633..e9159d3 100644 --- a/README.md +++ b/README.md @@ -1574,12 +1574,15 @@ The html page should look like - Example plot - - <> -
- - + + Example plot + <> + + +
+ + + ``` diff --git a/src/js/example-plot.html b/src/js/example-plot.html index 086a0f0..a31f9c0 100644 --- a/src/js/example-plot.html +++ b/src/js/example-plot.html @@ -1,21 +1,24 @@ - Example plot + + Example plot + + + + + + + + + + + + + + +
- - - - - - - - - - - - -
- - + + \ No newline at end of file From 057004e36dd293244e3c99064b4736ecfab8e402 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Fri, 5 Jun 2020 14:04:07 +0200 Subject: [PATCH 14/23] Squash more sonarcloud issues --- README.md | 46 ++++++++++++++++++----------- src/js/example-app.html | 29 ++++++++++-------- src/js/example-jsonschema-form.html | 29 ++++++++++-------- src/js/example-web-worker.html | 41 +++++++++++++------------ src/js/example.html | 35 ++++++++++++---------- src/js/jsonschema-app.js | 4 +-- src/js/plot-app.js | 4 +-- 7 files changed, 106 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index e9159d3..e040f16 100644 --- a/README.md +++ b/README.md @@ -766,11 +766,14 @@ To be able to use the `createModule` function, we will import the `newtonraphson ```{.html file=src/js/example.html} - - - + + + Example + + + ``` @@ -878,10 +881,13 @@ Like before we need a HTML page to run the JavaScript, but now we don't need to ```{.html file=src/js/example-web-worker.html} - - + + + Example web worker + + ``` @@ -920,8 +926,11 @@ we implement the React application in the `app.js` file. ```{.html file=src/js/example-app.html} - - <> + + + Example React application + <> +
@@ -1177,8 +1186,11 @@ To render the application we need a HTML page. We will reuse the imports we did ```{.html file=src/js/example-jsonschema-form.html} - - <> + + + Example JSON schema powered form + <> +
@@ -1249,12 +1261,12 @@ The `handleSubmit` function recieves the form input values and use the web worke // this JavaScript snippet is appended to <> const [root, setRoot] = React.useState(undefined); -function handleSubmit({formData}, event) { +function handleSubmit(submission, event) { event.preventDefault(); const worker = new Worker('worker.js'); worker.postMessage({ type: 'CALCULATE', - payload: formData + payload: submission.formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { @@ -1462,12 +1474,12 @@ To handle the submit we will start a worker, send the form data to the worker, r // this JavaScript snippet is appended to <> const [roots, setRoots] = React.useState([]); -function handleSubmit({formData}, event) { +function handleSubmit(submission, event) { event.preventDefault(); const worker = new Worker('worker-sweep.js'); worker.postMessage({ type: 'CALCULATE', - payload: formData + payload: submission.formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { diff --git a/src/js/example-app.html b/src/js/example-app.html index 79cfa00..f9d45f7 100644 --- a/src/js/example-app.html +++ b/src/js/example-app.html @@ -1,18 +1,21 @@ - - - - - - - - - - - - - + + + Example React application + + + + + + + + + + + + +
diff --git a/src/js/example-jsonschema-form.html b/src/js/example-jsonschema-form.html index 12e39e2..f9780ac 100644 --- a/src/js/example-jsonschema-form.html +++ b/src/js/example-jsonschema-form.html @@ -1,18 +1,21 @@ - - - - - - - - - - - - - + + + Example JSON schema powered form + + + + + + + + + + + + +
diff --git a/src/js/example-web-worker.html b/src/js/example-web-worker.html index 170077a..638a1e6 100644 --- a/src/js/example-web-worker.html +++ b/src/js/example-web-worker.html @@ -1,23 +1,26 @@ - - + + \ No newline at end of file diff --git a/src/js/example.html b/src/js/example.html index 3ec769c..571503a 100644 --- a/src/js/example.html +++ b/src/js/example.html @@ -1,19 +1,22 @@ - - - + + + Example + + + \ No newline at end of file diff --git a/src/js/jsonschema-app.js b/src/js/jsonschema-app.js index 0237341..e625ba5 100644 --- a/src/js/jsonschema-app.js +++ b/src/js/jsonschema-app.js @@ -40,12 +40,12 @@ function App() { // this JavaScript snippet is appended to <> const [root, setRoot] = React.useState(undefined); - function handleSubmit({formData}, event) { + function handleSubmit(submission, event) { event.preventDefault(); const worker = new Worker('worker.js'); worker.postMessage({ type: 'CALCULATE', - payload: formData + payload: submission.formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { diff --git a/src/js/plot-app.js b/src/js/plot-app.js index 18ae571..2916ae0 100644 --- a/src/js/plot-app.js +++ b/src/js/plot-app.js @@ -89,12 +89,12 @@ function App() { // this JavaScript snippet is appended to <> const [roots, setRoots] = React.useState([]); - function handleSubmit({formData}, event) { + function handleSubmit(submission, event) { event.preventDefault(); const worker = new Worker('worker-sweep.js'); worker.postMessage({ type: 'CALCULATE', - payload: formData + payload: submission.formData }); worker.onmessage = function(message) { if (message.data.type === 'RESULT') { From 7fe3c8e29399ce115a5fbd5977d17d297ade1518 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Fri, 5 Jun 2020 14:13:16 +0200 Subject: [PATCH 15/23] Added vega plot to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b06f3c9..662bc41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * JSON schema powered form ([#27](https://github.com/NLESC-JCER/cpp2wasm/issues/27)) * WebAssembly module ([#35](https://github.com/NLESC-JCER/cpp2wasm/issues/35)) +* Vega plot ([#15](https://github.com/NLESC-JCER/cpp2wasm/issues/15)) ## [0.1.0] - 2020-06-04 From e4c973abea44166f65d3f2f75297c62c27ac4706 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:14:03 +0200 Subject: [PATCH 16/23] Update TESTING.md Co-authored-by: Faruk D. --- TESTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TESTING.md b/TESTING.md index a698d82..b7fced2 100644 --- a/TESTING.md +++ b/TESTING.md @@ -7,7 +7,7 @@ Cypress can simulate user behavior such as clicking buttons etc. and checks expe In the following examples, we test if the example web pages render the answer `-1.00` when they are visited. -To visit a web page we need to start a web server with +To visit a web page we need to start a simple web server with using Python ```shell python3 -m http.server 8000 From e3c7889d710e3a717f3a1110ac0914579cf203f8 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:17:51 +0200 Subject: [PATCH 17/23] Update TESTING.md Co-authored-by: Faruk D. --- TESTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TESTING.md b/TESTING.md index b7fced2..0957620 100644 --- a/TESTING.md +++ b/TESTING.md @@ -44,7 +44,7 @@ Let us also change the initial guess value. describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - // In initial guess input field type 0 to append 0 to default -20 so the value is -200 + // We append a 0 to the guess input field so it becomes -200 cy.get('input[name=guess]').type('0'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); From 118e0c591445f796b3f6ddd8abd9599acb6812f1 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:18:58 +0200 Subject: [PATCH 18/23] Update TESTING.md --- TESTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TESTING.md b/TESTING.md index 0957620..41cbbf7 100644 --- a/TESTING.md +++ b/TESTING.md @@ -52,7 +52,7 @@ describe('src/js/example-app.html', () => { }); ``` -And another test for the full application, but now with [JSON schema powered form](http://localhost:8000/src/js/example-jsonschema-form.html). +And another test same as before, but now with [JSON schema powered form](http://localhost:8000/src/js/example-jsonschema-form.html). ```{.js file=cypress/integration/example-jsonschema-form_spec.js} describe('src/js/example-jsonschema-form.html', () => { From 7717c433c7d0eb6d3889387d08db96761a1e3777 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:20:16 +0200 Subject: [PATCH 19/23] Update TESTING.md Co-authored-by: Faruk D. --- TESTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TESTING.md b/TESTING.md index 41cbbf7..bd056d3 100644 --- a/TESTING.md +++ b/TESTING.md @@ -52,7 +52,7 @@ describe('src/js/example-app.html', () => { }); ``` -And another test same as before, but now with [JSON schema powered form](http://localhost:8000/src/js/example-jsonschema-form.html). +And similar test to the previous one, but now with [JSON schema powered form](http://localhost:8000/src/js/example-jsonschema-form.html). ```{.js file=cypress/integration/example-jsonschema-form_spec.js} describe('src/js/example-jsonschema-form.html', () => { From 5c6db48de7e787ac5d3c477c9a4e301edc1b6de8 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:22:15 +0200 Subject: [PATCH 20/23] Update TESTING.md Co-authored-by: Faruk D. --- TESTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TESTING.md b/TESTING.md index bd056d3..d7790bb 100644 --- a/TESTING.md +++ b/TESTING.md @@ -58,7 +58,8 @@ And similar test to the previous one, but now with [JSON schema powered form](ht describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - // The JSON schema powered form uses a hierarchy of identifers for each input field starting with `root`, as the `epsilon` input field is a direct child of root it has `root_epsilon` as an identifier + // The JSON schema powered form uses a hierarchy of identifiers for each input field starting with `root` + // As the `epsilon` input field is a direct child of root, it has `root_epsilon` as an identifier const input_selector = 'input[id=root_epsilon]'; // In initial guess input field replace default value of initial guess with 0.1 cy.get(input_selector).type('{selectall}0.1'); From be88118842ec88e874dbde40f68344565c598914 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:22:33 +0200 Subject: [PATCH 21/23] entangle --- cypress/integration/example-app_spec.js | 2 +- cypress/integration/example-jsonschema-form_spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cypress/integration/example-app_spec.js b/cypress/integration/example-app_spec.js index fc8f47c..f0af7df 100644 --- a/cypress/integration/example-app_spec.js +++ b/cypress/integration/example-app_spec.js @@ -1,7 +1,7 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - // In initial guess input field type 0 to append 0 to default -20 so the value is -200 + // We append a 0 to the guess input field so it becomes -200 cy.get('input[name=guess]').type('0'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); diff --git a/cypress/integration/example-jsonschema-form_spec.js b/cypress/integration/example-jsonschema-form_spec.js index 82d4935..c3bb5b1 100644 --- a/cypress/integration/example-jsonschema-form_spec.js +++ b/cypress/integration/example-jsonschema-form_spec.js @@ -1,7 +1,8 @@ describe('src/js/example-jsonschema-form.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-jsonschema-form.html'); - // The JSON schema powered form uses a hierarchy of identifers for each input field starting with `root`, as the `epsilon` input field is a direct child of root it has `root_epsilon` as an identifier + // The JSON schema powered form uses a hierarchy of identifiers for each input field starting with `root` + // As the `epsilon` input field is a direct child of root, it has `root_epsilon` as an identifier const input_selector = 'input[id=root_epsilon]'; // In initial guess input field replace default value of initial guess with 0.1 cy.get(input_selector).type('{selectall}0.1'); From f1e0b2282cf2ef5f7371d98370d6f649b9dc376b Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:31:03 +0200 Subject: [PATCH 22/23] Apply suggestions from code review Co-authored-by: Faruk D. --- TESTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TESTING.md b/TESTING.md index d7790bb..3177a48 100644 --- a/TESTING.md +++ b/TESTING.md @@ -44,7 +44,7 @@ Let us also change the initial guess value. describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - // We append a 0 to the guess input field so it becomes -200 + // The initial value of the guess input field is -20 so we append a 0 and it becomes -200 cy.get('input[name=guess]').type('0'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); @@ -61,7 +61,7 @@ describe('src/js/example-jsonschema-form.html', () => { // The JSON schema powered form uses a hierarchy of identifiers for each input field starting with `root` // As the `epsilon` input field is a direct child of root, it has `root_epsilon` as an identifier const input_selector = 'input[id=root_epsilon]'; - // In initial guess input field replace default value of initial guess with 0.1 + // In initial guess input field we replace the default value with 0.1 cy.get(input_selector).type('{selectall}0.1'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); From 20d6f3e1d31b3825e21dd5262891a4d96dd37bfe Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 8 Jun 2020 16:31:59 +0200 Subject: [PATCH 23/23] entangle again --- cypress/integration/example-app_spec.js | 2 +- cypress/integration/example-jsonschema-form_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/integration/example-app_spec.js b/cypress/integration/example-app_spec.js index f0af7df..d52c07c 100644 --- a/cypress/integration/example-app_spec.js +++ b/cypress/integration/example-app_spec.js @@ -1,7 +1,7 @@ describe('src/js/example-app.html', () => { it('should render -1.00', () => { cy.visit('http://localhost:8000/src/js/example-app.html'); - // We append a 0 to the guess input field so it becomes -200 + // The initial value of the guess input field is -20 so we append a 0 and it becomes -200 cy.get('input[name=guess]').type('0'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00'); diff --git a/cypress/integration/example-jsonschema-form_spec.js b/cypress/integration/example-jsonschema-form_spec.js index c3bb5b1..80c7209 100644 --- a/cypress/integration/example-jsonschema-form_spec.js +++ b/cypress/integration/example-jsonschema-form_spec.js @@ -4,7 +4,7 @@ describe('src/js/example-jsonschema-form.html', () => { // The JSON schema powered form uses a hierarchy of identifiers for each input field starting with `root` // As the `epsilon` input field is a direct child of root, it has `root_epsilon` as an identifier const input_selector = 'input[id=root_epsilon]'; - // In initial guess input field replace default value of initial guess with 0.1 + // In initial guess input field we replace the default value with 0.1 cy.get(input_selector).type('{selectall}0.1'); cy.contains('Submit').click(); cy.get('#answer').contains('-1.00');