Skip to content

Commit

Permalink
Add calculator collection module
Browse files Browse the repository at this point in the history
String Calculator implementations can be added or removed from this
collection without requiring changes to any other module or test except
for main.test.js. Even then, updating that test may not be strictly
required.

---

This will also help to solve the problem of having to build a new Docker
image to exercise the frontend and backend together. I'll eventually add
a calculator implementation that can point at a separately running
Tomcat backend.
  • Loading branch information
mbland committed Dec 18, 2023
1 parent 91b3040 commit 935202d
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 18 deletions.
2 changes: 1 addition & 1 deletion strcalc/src/main/frontend/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class App {
* @param {object} params - parameters made available to all initializers
* @param {Element} params.appElem - parent Element containing all components
* @param {string} params.apiUrl - API backend server URL
* @param {Function} params.postForm - posts form data to API
* @param {object} params.calculators - calculator implementations
*/
init(params) {
// In this example application, none of the components depend on one
Expand Down
9 changes: 7 additions & 2 deletions strcalc/src/main/frontend/components/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ import StringCalculatorPage from '../test/page'
// @vitest-environment jsdom
describe('initial state after calling App.init()', () => {
const page = StringCalculatorPage.new()
const calculators = {
'first': { label: 'First calculator', impl: null },
'second': { label: 'Second calculator', impl: null },
'third': { label: 'Third calculator', impl: null }
}

afterEach(() => page.clear())
afterAll(() => page.remove())

test('contains the "Hello, World!" placeholder', async () => {
new App().init({ appElem: page.appElem })
test('contains the "Hello, World!" title', async () => {
new App().init({ appElem: page.appElem, calculators })

const e = page.title()
expect(e.textContent).toContain('Hello, World!')
Expand Down
2 changes: 1 addition & 1 deletion strcalc/src/main/frontend/components/calculator.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<input type="text" name="numbers" id="numbers" required />
<fieldset class="impl">
<p>Select which implementation to use:</p>
{{#each implementations}}{{#with this}}
{{#each calcOptions}}{{#with this}}
<label for="impl-{{ value }}">{{ label }}</label>
<input type="radio" id="impl-{{ value }}" name="impl" value="{{ value }}"
{{#if @first}}checked{{/if}}/>{{#unless @last}}<br/>{{/unless}}
Expand Down
20 changes: 10 additions & 10 deletions strcalc/src/main/frontend/components/calculator.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,31 @@ export default class Calculator {
* @param {object} params - parameters made available to all initializers
* @param {Element} params.appElem - parent Element containing all components
* @param {string} params.apiUrl - API backend server URL
* @param {Function} params.postForm - posts form data to API
* @param {object} params.calculators - calculator implementations
*/
init({ appElem, apiUrl, postForm }) {
const implementations = [
{ label: 'Tomcat backend (Java)', value: 'java' },
{ label: 'In-browser frontend (JavaScript)', value: 'javascript' }
]
const t = Template({ apiUrl, implementations })
init({ appElem, apiUrl, calculators }) {
const calcOptions = Object.entries(calculators)
.map(([k, v]) => ({ value: k, label: v.label }))
const t = Template({ apiUrl, calcOptions })
const [ form, resultElem ] = t.children

appElem.appendChild(t)
document.querySelector('#numbers').focus()
form.addEventListener(
'submit', e => Calculator.#submitRequest(e, resultElem, postForm)
'submit', e => Calculator.#submitRequest(e, resultElem, calculators)
)
}

// https://simonplend.com/how-to-use-fetch-to-post-form-data-as-json-to-your-api/
static async #submitRequest(event, resultElem, postForm) {
static async #submitRequest(event, resultElem, calculators) {
event.preventDefault()

const form = event.currentTarget
const selected = form.querySelector('input[name="impl"]:checked').value
const result = resultElem.querySelector('p')

try {
const response = await postForm(event.currentTarget)
const response = await calculators[selected].impl(form)
result.textContent = `Result: ${response.result}`
} catch (err) {
result.textContent = err
Expand Down
11 changes: 9 additions & 2 deletions strcalc/src/main/frontend/components/calculator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Calculator from './calculator'
import { afterAll, afterEach, describe, expect, test, vi } from 'vitest'
import { resolvedUrl } from '../test/helpers.js'
import { resolvedUrl } from '../test/helpers'
import StringCalculatorPage from '../test/page'

// @vitest-environment jsdom
Expand All @@ -16,7 +16,14 @@ describe('Calculator', () => {

const setup = () => {
const postForm = vi.fn()
new Calculator().init({ appElem: page.appElem, apiUrl: './add', postForm })
const calculators = {
'api': { label: 'API', impl: postForm },
'browser': { label: 'Browser', impl: () => {} }
}

new Calculator().init({
appElem: page.appElem, apiUrl: './add', calculators
})
return { page, postForm }
}

Expand Down
15 changes: 15 additions & 0 deletions strcalc/src/main/frontend/components/calculators.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { postForm } from './request'

/**
* Collection of production String Calculator implementations
*/
export default {
'api': { label: 'Tomcat backend API (Java)', impl: postForm },
'browser': { label: 'In-browser (JavaScript)', impl: postForm }
}
4 changes: 2 additions & 2 deletions strcalc/src/main/frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* @module main
*/
import App from './components/app.js'
import { postForm } from './components/request'
import calculators from './components/calculators'

/**
* Calls the app initializer with production parameters.
Expand All @@ -30,7 +30,7 @@ document.addEventListener(
'DOMContentLoaded',
() => {
const appElem = document.querySelector('#app')
new App().init({ appElem, apiUrl: './add', postForm })
new App().init({ appElem, apiUrl: './add', calculators })
},
{ once: true }
)

0 comments on commit 935202d

Please sign in to comment.