-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add scenario integration tests for appearances and body classes
- Loading branch information
1 parent
fc776a9
commit 4844b7d
Showing
9 changed files
with
686 additions
and
18 deletions.
There are no files selected for viewing
9 changes: 9 additions & 0 deletions
9
packages/common/src/lib/type-assertions/assertUnknownObject.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
type UnknownObject = Record<PropertyKey, unknown>; | ||
|
||
type AssertUnknownObject = (value: unknown) => asserts value is UnknownObject; | ||
|
||
export const assertUnknownObject: AssertUnknownObject = (value) => { | ||
if (typeof value !== 'object' || value == null) { | ||
throw new Error('Not an object'); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import type { AssertIs } from '../../../types/assertions/AssertIs.ts'; | ||
|
||
type ArrayItemAssertion<T> = (item: unknown) => asserts item is T; | ||
|
||
export const arrayOfAssertion = <T>( | ||
assertItem: ArrayItemAssertion<T>, | ||
itemTypeDescription: string | ||
): AssertIs<readonly T[]> => { | ||
return (value) => { | ||
if (!Array.isArray(value)) { | ||
throw new Error(`Not an array of ${itemTypeDescription}: value itself is not an array`); | ||
} | ||
|
||
for (const [index, item] of value.entries()) { | ||
try { | ||
assertItem(item); | ||
} catch { | ||
throw new Error( | ||
`Not an array of ${itemTypeDescription}: item at index ${index} not an instance` | ||
); | ||
} | ||
} | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import type { DeriveStaticVitestExpectExtension } from '@getodk/common/test/assertions/helpers.ts'; | ||
import { | ||
AsymmetricTypedExpectExtension, | ||
extendExpect, | ||
} from '@getodk/common/test/assertions/helpers.ts'; | ||
import type { AnyNode } from '@getodk/xforms-engine'; | ||
import { expect } from 'vitest'; | ||
import { assertArrayOfStrings, assertEngineNode, assertString } from './shared-type-assertions.ts'; | ||
|
||
const hasAppearance = (node: AnyNode, appearance: string): boolean => { | ||
return node.appearances?.[appearance] === true; | ||
}; | ||
|
||
const appearanceExtensions = extendExpect(expect, { | ||
toHaveAppearance: new AsymmetricTypedExpectExtension( | ||
assertEngineNode, | ||
assertString, | ||
(actual, expected) => { | ||
if (hasAppearance(actual, expected)) { | ||
return true; | ||
} | ||
|
||
return new Error( | ||
`Node ${actual.currentState.reference} does not have appearance "${expected}"` | ||
); | ||
} | ||
), | ||
|
||
notToHaveAppearance: new AsymmetricTypedExpectExtension( | ||
assertEngineNode, | ||
assertString, | ||
(actual, expected) => { | ||
if (hasAppearance(actual, expected)) { | ||
return new Error( | ||
`Node ${actual.currentState.reference} has appearance "${expected}", which was not expected` | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
), | ||
|
||
toYieldAppearances: new AsymmetricTypedExpectExtension( | ||
assertEngineNode, | ||
assertArrayOfStrings, | ||
(actual, expected) => { | ||
const yielded = new Set<string>(); | ||
|
||
for (const appearance of actual.appearances ?? []) { | ||
yielded.add(appearance); | ||
} | ||
|
||
const notYielded = expected.filter((item) => { | ||
return !yielded.has(item); | ||
}); | ||
|
||
if (notYielded.length === 0) { | ||
return true; | ||
} | ||
|
||
return new Error( | ||
`Node ${actual.currentState.reference} did not yield expected appearances ${notYielded.join(', ')}` | ||
); | ||
} | ||
), | ||
}); | ||
|
||
type AppearanceExtensions = typeof appearanceExtensions; | ||
|
||
declare module 'vitest' { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
interface Assertion<T = any> extends DeriveStaticVitestExpectExtension<AppearanceExtensions, T> {} | ||
interface AsymmetricMatchersContaining | ||
extends DeriveStaticVitestExpectExtension<AppearanceExtensions> {} | ||
} |
75 changes: 75 additions & 0 deletions
75
packages/scenario/src/assertion/extensions/body-classes.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import type { DeriveStaticVitestExpectExtension } from '@getodk/common/test/assertions/helpers.ts'; | ||
import { | ||
AsymmetricTypedExpectExtension, | ||
extendExpect, | ||
} from '@getodk/common/test/assertions/helpers.ts'; | ||
import type { RootNode } from '@getodk/xforms-engine'; | ||
import { expect } from 'vitest'; | ||
import { assertArrayOfStrings, assertRootNode, assertString } from './shared-type-assertions.ts'; | ||
|
||
const hasClass = (node: RootNode, className: string): boolean => { | ||
return node.classes?.[className] === true; | ||
}; | ||
|
||
const bodyClassesExtensions = extendExpect(expect, { | ||
toHaveClass: new AsymmetricTypedExpectExtension( | ||
assertRootNode, | ||
assertString, | ||
(actual, expected) => { | ||
if (hasClass(actual, expected)) { | ||
return true; | ||
} | ||
|
||
return new Error( | ||
`RootNode ${actual.currentState.reference} does not have class "${expected}"` | ||
); | ||
} | ||
), | ||
|
||
notToHaveClass: new AsymmetricTypedExpectExtension( | ||
assertRootNode, | ||
assertString, | ||
(actual, expected) => { | ||
if (hasClass(actual, expected)) { | ||
return new Error( | ||
`RootNode ${actual.currentState.reference} has class "${expected}", which was not expected` | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
), | ||
|
||
toYieldClasses: new AsymmetricTypedExpectExtension( | ||
assertRootNode, | ||
assertArrayOfStrings, | ||
(actual, expected) => { | ||
const yielded = new Set<string>(); | ||
|
||
for (const className of actual.classes) { | ||
yielded.add(className); | ||
} | ||
|
||
const notYielded = expected.filter((item) => { | ||
return !yielded.has(item); | ||
}); | ||
|
||
if (notYielded.length === 0) { | ||
return true; | ||
} | ||
|
||
return new Error( | ||
`RootNode ${actual.currentState.reference} did not yield expected classes ${notYielded.join(', ')}` | ||
); | ||
} | ||
), | ||
}); | ||
|
||
type BodyClassExtensions = typeof bodyClassesExtensions; | ||
|
||
declare module 'vitest' { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
interface Assertion<T = any> extends DeriveStaticVitestExpectExtension<BodyClassExtensions, T> {} | ||
interface AsymmetricMatchersContaining | ||
extends DeriveStaticVitestExpectExtension<BodyClassExtensions> {} | ||
} |
55 changes: 55 additions & 0 deletions
55
packages/scenario/src/assertion/extensions/shared-type-assertions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { assertUnknownObject } from '@getodk/common/lib/type-assertions/assertUnknownObject.ts'; | ||
import { arrayOfAssertion } from '@getodk/common/test/assertions/arrayOfAssertion.ts'; | ||
import { typeofAssertion } from '@getodk/common/test/assertions/typeofAssertion.ts'; | ||
import type { AnyNode, RootNode } from '@getodk/xforms-engine'; | ||
|
||
type AssertRootNode = (node: unknown) => asserts node is RootNode; | ||
|
||
export const assertRootNode: AssertRootNode = (node) => { | ||
assertUnknownObject(node); | ||
|
||
const maybeRootNode = node as Partial<RootNode>; | ||
|
||
if ( | ||
maybeRootNode.nodeType !== 'root' || | ||
typeof maybeRootNode.setLanguage !== 'function' || | ||
typeof maybeRootNode.currentState !== 'object' || | ||
maybeRootNode.currentState == null | ||
) { | ||
throw new Error('Node is not a `RootNode`'); | ||
} | ||
}; | ||
|
||
type AssertEngineNode = (node: unknown) => asserts node is AnyNode; | ||
|
||
type AnyNodeType = AnyNode['nodeType']; | ||
type NonRootNodeType = Exclude<AnyNodeType, 'root'>; | ||
|
||
const nonRootNodeTypes = new Set<NonRootNodeType>([ | ||
'string', | ||
'select', | ||
'subtree', | ||
'group', | ||
'repeat-range', | ||
'repeat-instance', | ||
]); | ||
|
||
export const assertEngineNode: AssertEngineNode = (node) => { | ||
assertUnknownObject(node); | ||
|
||
const maybeNode = node as Partial<AnyNode>; | ||
|
||
assertRootNode(maybeNode.root); | ||
|
||
if (maybeNode === maybeNode.root) { | ||
return; | ||
} | ||
|
||
if (!nonRootNodeTypes.has(maybeNode.nodeType as NonRootNodeType)) { | ||
throw new Error('Not an engine node'); | ||
} | ||
}; | ||
|
||
export const assertString = typeofAssertion('string'); | ||
|
||
export const assertArrayOfStrings = arrayOfAssertion(assertString, 'string'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.