Skip to content

Commit c7fc862

Browse files
committed
Unit test case
1 parent 2f20901 commit c7fc862

4 files changed

Lines changed: 251 additions & 1 deletion

File tree

packages/app/src/components/workbench/compare.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,9 +808,12 @@ export class DevtoolsCompare extends Element {
808808
: isFailureSite
809809
? step?.error?.matcherResult?.actual
810810
: undefined
811-
const assertionMessage = isFailureSite
811+
const rawAssertion = isFailureSite
812812
? step?.error?.matcherResult?.message || step?.error?.message
813813
: undefined
814+
const assertionMessage = rawAssertion
815+
? cleanErrorMessage(rawAssertion)
816+
: undefined
814817
// Fallback: extract the expected from the Cucumber step text.
815818
const stepText = step?.fullTitle || step?.title || ''
816819
const fallbackExpected =
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { describe, it, expect } from 'vitest'
2+
3+
import {
4+
pairSteps,
5+
commandsEqual,
6+
classifyDivergence,
7+
extractExpectedFromStepText,
8+
safeJson
9+
} from '../src/components/workbench/compare/compareUtils.js'
10+
11+
const cmd = (
12+
command: string,
13+
args: unknown[] = [],
14+
extra: Record<string, unknown> = {}
15+
) =>
16+
({
17+
command,
18+
args,
19+
result: null,
20+
timestamp: 0,
21+
callSource: '',
22+
...extra
23+
}) as never
24+
25+
describe('compareUtils', () => {
26+
it('commandsEqual ignores `result` (W3C element refs drift across sessions) but catches command/args/error diffs', () => {
27+
// Identical → equal
28+
expect(commandsEqual(cmd('url', ['/a']), cmd('url', ['/a']))).toBe(true)
29+
// Same call but a different element-ref in result → still equal
30+
const refA = cmd('$', ['#x'], {
31+
result: { 'element-6066-11e4-a52e-4f735466cecf': 'a' }
32+
})
33+
const refB = cmd('$', ['#x'], {
34+
result: { 'element-6066-11e4-a52e-4f735466cecf': 'b' }
35+
})
36+
expect(commandsEqual(refA, refB)).toBe(true)
37+
// Real divergences
38+
expect(commandsEqual(cmd('url'), cmd('click'))).toBe(false)
39+
expect(commandsEqual(cmd('url', ['/a']), cmd('url', ['/b']))).toBe(false)
40+
expect(
41+
commandsEqual(
42+
cmd('click', [], { error: { message: 'boom' } }),
43+
cmd('click')
44+
)
45+
).toBe(false)
46+
// Missing side
47+
expect(commandsEqual(undefined, cmd('url'))).toBe(false)
48+
})
49+
50+
it('classifyDivergence labels every kind correctly', () => {
51+
expect(classifyDivergence(cmd('url'), cmd('url'))).toBe('none')
52+
expect(classifyDivergence(cmd('url'), undefined)).toBe('missing')
53+
expect(classifyDivergence(cmd('url'), cmd('click'))).toBe('commandName')
54+
expect(classifyDivergence(cmd('url', ['/a']), cmd('url', ['/b']))).toBe(
55+
'args'
56+
)
57+
expect(
58+
classifyDivergence(
59+
cmd('click', [], { error: { message: 'boom' } }),
60+
cmd('click')
61+
)
62+
).toBe('error')
63+
})
64+
65+
it('pairSteps locks the fork bit once execution diverges and handles uneven lengths', () => {
66+
const pairs = pairSteps(
67+
[cmd('url'), cmd('a'), cmd('b'), cmd('c')],
68+
[cmd('url'), cmd('X'), cmd('Y')]
69+
)
70+
expect(pairs.map((p) => p.divergent)).toEqual([false, true, true, true])
71+
expect(pairs[3].baseline?.command).toBe('c')
72+
expect(pairs[3].latest).toBeUndefined()
73+
})
74+
75+
it('extractExpectedFromStepText pulls the parameterized tail from Cucumber Then steps', () => {
76+
expect(
77+
extractExpectedFromStepText(
78+
'1: Then I should see a flash message saying You logged in!'
79+
)
80+
).toBe('You logged in!')
81+
expect(extractExpectedFromStepText('')).toBeUndefined()
82+
})
83+
84+
it('safeJson stringifies, truncates long output, and survives cyclic refs', () => {
85+
expect(safeJson({ a: 1 })).toBe('{"a":1}')
86+
const truncated = safeJson('x'.repeat(2000))
87+
expect(truncated.endsWith('…')).toBe(true)
88+
const cyclic: Record<string, unknown> = {}
89+
cyclic.self = cyclic
90+
expect(safeJson(cyclic)).toBe('[object Object]')
91+
})
92+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { toMs, pickMin, pickMax } from '../../src/baseline/utils.js'
3+
4+
describe('baseline/utils', () => {
5+
it('toMs coerces Date / ISO / number to ms and rejects garbage', () => {
6+
const ms = 1700000000000
7+
expect(toMs(ms)).toBe(ms)
8+
expect(toMs(new Date(ms))).toBe(ms)
9+
expect(toMs('2025-01-15T10:00:00.000Z')).toBe(
10+
Date.parse('2025-01-15T10:00:00.000Z')
11+
)
12+
expect(toMs(null)).toBeUndefined()
13+
expect(toMs('not-a-date')).toBeUndefined()
14+
})
15+
16+
it('pickMin / pickMax tolerate undefined on either side', () => {
17+
expect(pickMin(undefined, 5)).toBe(5)
18+
expect(pickMin(7, 3)).toBe(3)
19+
expect(pickMin(undefined, undefined)).toBeUndefined()
20+
expect(pickMax(7, undefined)).toBe(7)
21+
expect(pickMax(3, 8)).toBe(8)
22+
})
23+
})
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { describe, it, expect, beforeEach } from 'vitest'
2+
3+
import { baselineStore } from '../src/baselineStore.js'
4+
5+
const SUITE_UID = 'suite-1'
6+
const TEST_UID = 'test-1'
7+
8+
function suite(opts: {
9+
start: number
10+
end?: number
11+
state?: 'passed' | 'failed' | 'pending' | 'running'
12+
childState?: 'passed' | 'failed'
13+
childError?: { message: string }
14+
}) {
15+
return [
16+
{
17+
[SUITE_UID]: {
18+
uid: SUITE_UID,
19+
title: 'My Suite',
20+
file: '/spec.ts',
21+
start: opts.start,
22+
end: opts.end,
23+
state: opts.state,
24+
tests: [
25+
{
26+
uid: TEST_UID,
27+
title: 'should do a thing',
28+
fullTitle: 'My Suite should do a thing',
29+
start: opts.start,
30+
end: opts.end,
31+
state: opts.childState,
32+
error: opts.childError
33+
}
34+
],
35+
suites: []
36+
}
37+
}
38+
]
39+
}
40+
41+
describe('baselineStore', () => {
42+
beforeEach(() => {
43+
baselineStore.resetActiveRun()
44+
baselineStore.clearAll()
45+
})
46+
47+
it('filters commands to the test time window and ignores commands outside it', () => {
48+
baselineStore.recordEvent('commands', [
49+
{ timestamp: 100, command: 'before', args: [] },
50+
{ timestamp: 250, command: 'inside', args: [] },
51+
{ timestamp: 900, command: 'after', args: [] }
52+
])
53+
baselineStore.recordEvent('suites', suite({ start: 200, end: 300 }))
54+
55+
const snap = baselineStore.snapshot(TEST_UID, 'test')!
56+
expect(snap.commands.map((c) => c.command)).toEqual(['inside'])
57+
expect(baselineStore.snapshot('does-not-exist', 'test')).toBeUndefined()
58+
})
59+
60+
it('replaces (not unions) the time window when a new run is detected', () => {
61+
baselineStore.recordEvent(
62+
'suites',
63+
suite({
64+
start: 100,
65+
end: 200,
66+
state: 'failed',
67+
childState: 'failed',
68+
childError: { message: 'old failure' }
69+
})
70+
)
71+
baselineStore.recordEvent('commands', [
72+
{ timestamp: 150, command: 'first-run', args: [] }
73+
])
74+
// Incoming.start > previous.end → isNewRun
75+
baselineStore.recordEvent(
76+
'suites',
77+
suite({ start: 500, end: 600, state: 'passed', childState: 'passed' })
78+
)
79+
baselineStore.recordEvent('commands', [
80+
{ timestamp: 550, command: 'second-run', args: [] }
81+
])
82+
83+
const snap = baselineStore.snapshot(TEST_UID, 'test')!
84+
expect(snap.window).toEqual({ start: 500, end: 600 })
85+
expect(snap.commands.map((c) => c.command)).toEqual(['second-run'])
86+
// State + error reset on the new run — no stale failure leak.
87+
expect(snap.test.state).toBe('passed')
88+
expect(snap.test.error).toBeUndefined()
89+
})
90+
91+
it("rolls a failing descendant's state + error up to a suite-scope snapshot", () => {
92+
baselineStore.recordEvent('commands', [
93+
{ timestamp: 150, command: 'click', args: [] }
94+
])
95+
baselineStore.recordEvent(
96+
'suites',
97+
suite({
98+
start: 100,
99+
end: 200,
100+
childState: 'failed',
101+
childError: { message: 'expected X, got Y' }
102+
})
103+
)
104+
105+
const snap = baselineStore.snapshot(SUITE_UID, 'suite')!
106+
expect(snap.test.state).toBe('failed')
107+
expect(snap.test.error?.message).toBe('expected X, got Y')
108+
expect(snap.steps?.[0]).toMatchObject({
109+
uid: TEST_UID,
110+
state: 'failed'
111+
})
112+
})
113+
114+
it('preserve refuses an empty-command snapshot (the 409 case)', () => {
115+
baselineStore.recordEvent('suites', suite({ start: 100, end: 200 }))
116+
expect(baselineStore.preserve(TEST_UID, 'test')).toBeUndefined()
117+
expect(baselineStore.get(TEST_UID)).toBeUndefined()
118+
})
119+
120+
it('preserve stores; clearAll wipes every baseline and returns their uids', () => {
121+
baselineStore.recordEvent('commands', [
122+
{ timestamp: 150, command: 'click', args: [] }
123+
])
124+
baselineStore.recordEvent('suites', suite({ start: 100, end: 200 }))
125+
126+
const attempt = baselineStore.preserve(TEST_UID, 'test')!
127+
expect(baselineStore.get(TEST_UID)).toBe(attempt)
128+
129+
expect(baselineStore.clearAll()).toEqual([TEST_UID])
130+
expect(baselineStore.get(TEST_UID)).toBeUndefined()
131+
})
132+
})

0 commit comments

Comments
 (0)