Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions app/client/.eslintrc.json

This file was deleted.

53 changes: 53 additions & 0 deletions app/client/components/AppDialog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor
*/
// @vitest-environment happy-dom
import { test, expect, describe, vi } from 'vitest'

import { AppDialog } from './AppDialog'

describe('confirm', () => {
test('should close the dialog with "confirm" when isValid is true', () => {
const dialog = new AppDialog()
dialog.isValid = true
const closeFn = vi.fn()
dialog.dialog = { value: { close: closeFn } } as any

dialog.confirm()

expect(closeFn).toHaveBeenCalledWith('confirm')
})

test('should not close the dialog when isValid is false', () => {
const dialog = new AppDialog()
dialog.isValid = false
const closeFn = vi.fn()
dialog.dialog = { value: { close: closeFn } } as any

dialog.confirm()

expect(closeFn).not.toHaveBeenCalled()
})
})

describe('close', () => {
test('should dispatch a CustomEvent with detail "cancel" when not confirmed', () => {
const dialog = new AppDialog()
let received: CustomEvent | undefined
dialog.addEventListener('close', (e) => { received = e as CustomEvent })

dialog.close({ target: { returnValue: 'cancel' } } as any)

expect(received!.detail).toBe('cancel')
})

test('should dispatch a CustomEvent with detail "confirm" when confirmed', () => {
const dialog = new AppDialog()
let received: CustomEvent | undefined
dialog.addEventListener('close', (e) => { received = e as CustomEvent })

dialog.close({ target: { returnValue: 'confirm' } } as any)

expect(received!.detail).toBe('confirm')
})
})
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
'use strict'
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor

Component that renders a html dialog
*/
import { AppElement, html, css } from './AppElement.js'
import { AppElement, html, css } from './AppElement'
import { customElement, property } from 'lit/decorators.js'
import { ref, createRef } from 'lit/directives/ref.js'
import { ref, createRef, Ref } from 'lit/directives/ref.js'

@customElement('app-dialog')
export class AppDialog extends AppElement {
constructor () {
super()
this.dialog = createRef()
}
dialog: Ref<HTMLDialogElement> = createRef()

static styles = css`
dialog {
Expand Down Expand Up @@ -72,10 +68,10 @@ export class AppDialog extends AppElement {
}
`
@property({ type: Boolean })
accessor isValid = true
isValid = true

@property({ type: Boolean, reflect: true })
accessor dialogOpen
dialogOpen: boolean | undefined

render () {
return html`
Expand All @@ -93,8 +89,8 @@ export class AppDialog extends AppElement {
`
}

close (event) {
if (event.target.returnValue !== 'confirm') {
close (event: Event) {
if ((event?.target as HTMLDialogElement).returnValue !== 'confirm') {
this.dispatchEvent(new CustomEvent('close', { detail: 'cancel' }))
} else {
this.dispatchEvent(new CustomEvent('close', { detail: 'confirm' }))
Expand All @@ -103,20 +99,20 @@ export class AppDialog extends AppElement {

confirm () {
if (this.isValid) {
this.dialog.value.close('confirm')
this.dialog.value?.close('confirm')
}
}

firstUpdated () {
this.dialog.value.showModal()
this.dialog.value?.showModal()
}

updated (changedProperties) {
updated (changedProperties: Map<string, unknown>) {
if (changedProperties.has('dialogOpen')) {
if (this.dialogOpen) {
this.dialog.value.showModal()
this.dialog.value?.showModal()
} else {
this.dialog.value.close()
this.dialog.value?.close()
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions app/client/components/AppElement.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor
*/
// @vitest-environment happy-dom
import { test, expect, describe } from 'vitest'

import { AppElement } from './AppElement'
import { customElement } from 'lit/decorators.js'

// AppElement has no @customElement decorator, so we register a test subclass
@customElement('test-app-element')
class TestAppElement extends AppElement {}

describe('sendEvent', () => {
test('should dispatch a CustomEvent with the correct type and detail', () => {
const el = new TestAppElement()
let received: CustomEvent | undefined
el.addEventListener('testEvent', (e) => { received = e as CustomEvent })

el.sendEvent('testEvent', { foo: 'bar' })

expect(received).toBeDefined()
expect(received!.detail).toEqual({ foo: 'bar' })
})

test('should set bubbles and composed to true', () => {
const el = new TestAppElement()
let received: CustomEvent | undefined
el.addEventListener('testEvent', (e) => { received = e as CustomEvent })

el.sendEvent('testEvent', null)

expect(received!.bubbles).toBe(true)
expect(received!.composed).toBe(true)
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use strict'
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor

Expand All @@ -10,7 +9,7 @@ export * from 'lit'

export class AppElement extends LitElement {
// a helper to dispatch events to the parent components
sendEvent (eventType, eventData) {
sendEvent (eventType: string, eventData: unknown) {
this.dispatchEvent(
new CustomEvent(eventType, {
detail: eventData,
Expand Down
52 changes: 52 additions & 0 deletions app/client/components/BatteryIcon.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor
*/
// @vitest-environment happy-dom
import { test, expect, describe } from 'vitest'

// The class is exported as DashboardMetric (naming bug in source)
import { DashboardMetric as BatteryIcon } from './BatteryIcon'

describe('battery width calculation', () => {
test('should compute batteryWidth as batteryLevel * 416 / 100', () => {
const el = new BatteryIcon()
el.batteryLevel = 50
// batteryWidth = 50 * 416 / 100 = 208
// We verify by checking the rendered SVG contains the computed width
// Trigger a render manually via the render method
const result = (el as any).render()
// The render returns a TemplateResult; we check the values array contains 208
expect(result.values).toContain(208)
})

test('should compute batteryWidth as 0 when batteryLevel is 0', () => {
const el = new BatteryIcon()
el.batteryLevel = 0
const result = (el as any).render()
expect(result.values).toContain(0)
})

test('should compute batteryWidth as 416 when batteryLevel is 100', () => {
const el = new BatteryIcon()
el.batteryLevel = 100
const result = (el as any).render()
expect(result.values).toContain(416)
})
})

describe('icon CSS class', () => {
test('should include low-battery when level is 25 or less', () => {
const el = new BatteryIcon()
el.batteryLevel = 25
const result = (el as any).render()
expect(result.values).toContain('icon low-battery')
})

test('should not include low-battery when level is above 25', () => {
const el = new BatteryIcon()
el.batteryLevel = 26
const result = (el as any).render()
expect(result.values).toContain('icon')
expect(result.values).not.toContain('icon low-battery')
})
})
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use strict'
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor

Component that renders a battery indicator
*/

import { AppElement, svg, css } from './AppElement.js'
import { AppElement, svg, css } from './AppElement'
import { customElement, property } from 'lit/decorators.js'

@customElement('battery-icon')
Expand All @@ -21,7 +20,7 @@ export class DashboardMetric extends AppElement {
`

@property({ type: Number })
accessor batteryLevel = 0
batteryLevel = 0

render () {
// 416 is the max width value of the battery bar in the SVG graphic
Expand Down
110 changes: 110 additions & 0 deletions app/client/components/DashboardForceCurve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Open Rowing Monitor, https://github.com/JaapvanEkris/openrowingmonitor
*/
// @vitest-environment happy-dom
import { test, expect, describe } from 'vitest'

import { DashboardForceCurve } from './DashboardForceCurve'

function createForceCurve (): DashboardForceCurve {
const el = new DashboardForceCurve()
return el
}

describe('_handleClick', () => {
test('should cycle division modes in order: 0 → 2 → 3 → 0', () => {
const el = createForceCurve()
const received: unknown[] = []
el.addEventListener('changeGuiSetting', (e) => {
received.push((e as CustomEvent).detail)
})

el.divisionMode = 0
el._handleClick()

el.divisionMode = 2
el._handleClick()

el.divisionMode = 3
el._handleClick()

expect(received).toEqual([
{ forceCurveDivisionMode: 2 },
{ forceCurveDivisionMode: 3 },
{ forceCurveDivisionMode: 0 }
])
})
})

describe('_updateDivisionLines', () => {
test('should compute correct positions for 2-way division', () => {
const el = createForceCurve()
el.value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
el.divisionMode = 2
// Mock chart with plugins
el._chart = {
options: { plugins: { divisionLines: { positions: [] } } }
} as any

el._updateDivisionLines()

// @ts-ignore
expect(el._chart!.options!.plugins!.divisionLines.positions).toEqual([5])
})

test('should compute correct positions for 3-way division', () => {
const el = createForceCurve()
el.value = [1, 2, 3, 4, 5, 6, 7, 8, 9]
el.divisionMode = 3
el._chart = {
options: { plugins: { divisionLines: { positions: [] } } }
} as any

el._updateDivisionLines()

// @ts-ignore
expect(el._chart!.options!.plugins!.divisionLines.positions).toEqual([3, 6])
})

test('should return an empty array for mode 0', () => {
const el = createForceCurve()
el.value = [1, 2, 3, 4, 5]
el.divisionMode = 0
el._chart = {
options: { plugins: { divisionLines: { positions: [] } } }
} as any

el._updateDivisionLines()

// @ts-ignore
expect(el._chart!.options!.plugins!.divisionLines.positions).toEqual([])
})
})

describe('shouldUpdate', () => {
test('should return true when updateForceCurve is true', () => {
const el = createForceCurve()
el.updateForceCurve = true
const changedProperties = new Map()

expect(el.shouldUpdate(changedProperties)).toBe(true)
})

test('should return true when divisionMode has changed', () => {
const el = createForceCurve()
el.updateForceCurve = false
el._chart = {} as any // chart exists, so that condition is false
const changedProperties = new Map([['divisionMode', 0]])

expect(el.shouldUpdate(changedProperties)).toBe(true)
})

test('should return false when nothing relevant has changed and chart exists', () => {
const el = createForceCurve()
el.updateForceCurve = false
el._chart = {} as any
const changedProperties = new Map()

expect(el.shouldUpdate(changedProperties)).toBe(false)
})
})
Loading
Loading