@harnessed-ts/ember 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/ember-query.d.ts +6 -1
- package/dist/ember-query.d.ts.map +1 -1
- package/dist/index.cjs +26 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @harnessed-ts/ember
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0ab9390: `upload()` no longer goes through user-event. user-event's setup patches the document for good, and an Ember test run shares one document, so after a test that uploaded a file, a number input in a later test read "42.50" back as "42.5". The driver now sets the files itself and fires `input` and `change`, keeping user-event's rules: a disabled input is skipped, files `accept` refuses are dropped, and an unchanged selection fires nothing. `press`, `hover` and `selectOption` still use user-event, so they still patch the document.
|
|
8
|
+
- Updated dependencies [ec06bdf]
|
|
9
|
+
- @harnessed-ts/core@0.7.0
|
|
10
|
+
- @harnessed-ts/resolve@0.7.0
|
|
11
|
+
|
|
3
12
|
## 0.6.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
package/dist/ember-query.d.ts
CHANGED
|
@@ -34,7 +34,12 @@ export declare class EmberQuery extends Query {
|
|
|
34
34
|
focus(options?: WaitOptions): Promise<void>;
|
|
35
35
|
blur(options?: WaitOptions): Promise<void>;
|
|
36
36
|
press(key: string, options?: WaitOptions): Promise<void>;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* What user-event's upload does — skip a disabled input, drop files `accept`
|
|
39
|
+
* refuses, fire nothing when the selection is unchanged — without user-event:
|
|
40
|
+
* its setup patches the document for good, and an Ember run shares one
|
|
41
|
+
* document, so a number input in a later test read "42.50" back as "42.5".
|
|
42
|
+
*/
|
|
38
43
|
upload(files: File | File[], options?: WaitOptions): Promise<void>;
|
|
39
44
|
text(options?: WaitOptions): Promise<string>;
|
|
40
45
|
inputValue(options?: WaitOptions): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ember-query.d.ts","sourceRoot":"","sources":["../src/ember-query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAkB,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAa,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"ember-query.d.ts","sourceRoot":"","sources":["../src/ember-query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAkB,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAa,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAsFlG;;;;;;;;;;;;;;GAcG;AACH,qBAAa,UAAW,SAAQ,KAAK;IAEjC,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,OAAO,GAAG,SAAS,EAC1C,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,QAAQ,EAAE,QAAQ;cAKD,KAAK,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK;IAInD,gFAAgF;IAChF,SAAS,CAAC,SAAS,IAAI,WAAW;IAIlC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAI9D,2EAA2E;IAC5D,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IASvB,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzD,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,OAAO,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7C,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5E,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3C,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3C,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IASvE;;;;;OAKG;IACY,MAAM,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBlE,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5C,UAAU,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAKlD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAItE,SAAS,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAclD,SAAS,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAQlD,SAAS,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAQlD,eAAe,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAKzD,WAAW,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAMxD,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/D,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;CAGxC"}
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,17 @@ function userFor(element) {
|
|
|
25
25
|
function toKeyboardInput(key) {
|
|
26
26
|
return key.length === 1 ? key : `{${key}}`;
|
|
27
27
|
}
|
|
28
|
+
function accepts(accept, file) {
|
|
29
|
+
if (accept === "") return true;
|
|
30
|
+
const normalize = (nameOrType) => nameOrType.toLowerCase().replace(/(\.|\/)jpg\b/g, "$1jpeg");
|
|
31
|
+
return normalize(accept).trim().split(/\s*,\s*/).some((token) => {
|
|
32
|
+
if (token.startsWith(".")) return normalize(file.name).endsWith(token);
|
|
33
|
+
if (["audio/*", "image/*", "video/*"].includes(token)) {
|
|
34
|
+
return normalize(file.type).startsWith(token.slice(0, -1));
|
|
35
|
+
}
|
|
36
|
+
return normalize(file.type) === token;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
28
39
|
async function waitUntil(condition, timeout) {
|
|
29
40
|
let refused;
|
|
30
41
|
await dom.waitFor(
|
|
@@ -111,12 +122,25 @@ var EmberQuery = class _EmberQuery extends core.Query {
|
|
|
111
122
|
await userFor(element).keyboard(toKeyboardInput(key));
|
|
112
123
|
await testHelpers.settled();
|
|
113
124
|
}
|
|
114
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* What user-event's upload does — skip a disabled input, drop files `accept`
|
|
127
|
+
* refuses, fire nothing when the selection is unchanged — without user-event:
|
|
128
|
+
* its setup patches the document for good, and an Ember run shares one
|
|
129
|
+
* document, so a number input in a later test read "42.50" back as "42.5".
|
|
130
|
+
*/
|
|
115
131
|
async upload(files, options) {
|
|
116
132
|
const list = Array.isArray(files) ? files : [files];
|
|
117
133
|
const input = resolve.fileInputOf(await this.element(options));
|
|
118
134
|
core.checkFileTarget(input, list.length, this.scope, this.selector);
|
|
119
|
-
|
|
135
|
+
if (input.matches(":disabled")) return;
|
|
136
|
+
const chosen = list.filter((file) => accepts(input.accept, file));
|
|
137
|
+
const current = [...input.files ?? []];
|
|
138
|
+
if (chosen.length === current.length && chosen.every((file, i) => file === current[i])) return;
|
|
139
|
+
const transfer = new input.ownerDocument.defaultView.DataTransfer();
|
|
140
|
+
for (const file of chosen) transfer.items.add(file);
|
|
141
|
+
input.files = transfer.files;
|
|
142
|
+
input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
143
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
120
144
|
await testHelpers.settled();
|
|
121
145
|
}
|
|
122
146
|
// --- observations -------------------------------------------------------
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/driver-id.ts","../src/ember-query.ts","../src/navigation.ts","../src/env.ts"],"names":["userEvent","waitForCondition","FrameEntryError","StrictModeViolation","Query","getRootElement","resolveOne","resolveAll","withNth","click","fillIn","settled","fileInputOf","checkFileTarget","isVisibleInLayout","enabledFrom","checkedFrom","boundingBoxOf","timeoutFor","countAll","registerDriver","currentURL","registerNavigation","visit"],"mappings":"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","file":"index.cjs","sourcesContent":["/** The registry key for this driver. Its own module so env and query share it without a cycle. */\nexport const EMBER_DRIVER = 'ember'\n","import { click, fillIn, getRootElement, settled } from '@ember/test-helpers'\nimport { Query, registerDriver } from '@harnessed-ts/core'\nimport type { BoundingBox, EnvConfig, Selector, WaitOptions, WaitState } from '@harnessed-ts/core'\nimport {\n checkedFrom,\n checkFileTarget,\n enabledFrom,\n nth as withNth,\n StrictModeViolation,\n timeoutFor,\n} from '@harnessed-ts/core'\nimport {\n boundingBoxOf,\n countAll,\n fileInputOf,\n FrameEntryError,\n isVisibleInLayout,\n resolveAll,\n resolveOne,\n} from '@harnessed-ts/resolve'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport userEvent from '@testing-library/user-event'\nimport type { UserEvent } from '@testing-library/user-event'\nimport { EMBER_DRIVER } from './driver-id'\nimport type { EmberEnv } from './env'\n\n/**\n * One user-event instance per document, kept so pointer state carries between\n * calls: hovering B after A has to leave A first.\n */\nconst users = new WeakMap<Document, UserEvent>()\n\nfunction userFor(element: Element): UserEvent {\n const document = element.ownerDocument\n let user = users.get(document)\n if (user === undefined) {\n user = userEvent.setup({ document })\n users.set(document, user)\n }\n return user\n}\n\n/** Playwright key names such as `Enter` map onto user-event's `{Enter}` syntax. */\nfunction toKeyboardInput(key: string): string {\n return key.length === 1 ? key : `{${key}}`\n}\n\n/**\n * Testing Library's `waitFor` retries every throw, but a frame that cannot be\n * entered never becomes enterable, and several matches never become one — so\n * either ends the wait at once. Not test-helpers' `waitUntil`: its callback is\n * synchronous, and every check here resolves through the async resolver.\n */\nasync function waitUntil(condition: () => Promise<void>, timeout: number): Promise<void> {\n let refused: FrameEntryError | StrictModeViolation | undefined\n await waitForCondition(\n async () => {\n try {\n await condition()\n } catch (error) {\n if (!(error instanceof FrameEntryError || error instanceof StrictModeViolation)) {\n throw error\n }\n refused = error\n }\n },\n { timeout },\n )\n if (refused !== undefined) throw refused\n}\n\n/**\n * Ember driver. Resolution is the shared resolver, so it agrees with every other\n * in-page driver. Every interaction resolves only once the app has settled —\n * runloop drained, rendering done, test waiters clear.\n *\n * Clicks and fills go through `@ember/test-helpers`, the way an Ember test\n * dispatches them. Keys, hover, focus and selects go through user-event, as in\n * the dom driver: test-helpers' versions dispatch events without a browser's\n * default actions (a pressed key never reaches the value), refuse elements a\n * browser would accept (a disabled control, a non-focusable dialog), and match\n * options by value only.\n *\n * Each helper is handed the resolved element, never a selector string, so\n * strictness and scope stay this library's rather than `querySelector`'s.\n */\nexport class EmberQuery extends Query {\n constructor(\n private readonly root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n ) {\n super(scope, selector)\n }\n\n protected override clone(selector: Selector): Query {\n return new EmberQuery(this.root, this.scope, selector)\n }\n\n /** Read per call: the root element belongs to the current test, not the env. */\n protected container(): HTMLElement {\n return (this.root ?? getRootElement()) as HTMLElement\n }\n\n protected element(options?: WaitOptions): Promise<HTMLElement> {\n return resolveOne(this.container(), this.scope, this.selector, options?.timeout)\n }\n\n /** Every match in one pass, each bound to its node. See `DomQuery.all`. */\n override async all(): Promise<Query[]> {\n return (await resolveAll(this.container(), this.scope, this.selector)).map(\n (element, index) =>\n new BoundEmberQuery(this.root, this.scope, withNth(this.selector, index), element),\n )\n }\n\n // --- interactions -------------------------------------------------------\n\n override async click(options?: WaitOptions): Promise<void> {\n await click(await this.element(options))\n }\n\n override async fill(value: string, options?: WaitOptions): Promise<void> {\n // fillIn replaces the value and fires input and change: Playwright's fill.\n await fillIn(await this.element(options), value)\n }\n\n override async clear(options?: WaitOptions): Promise<void> {\n await fillIn(await this.element(options), '')\n }\n\n override async check(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if (!(element as HTMLInputElement).checked) await click(element)\n }\n\n override async uncheck(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if ((element as HTMLInputElement).checked) await click(element)\n }\n\n override async selectOption(value: string | string[], options?: WaitOptions): Promise<void> {\n const element = (await this.element(options)) as HTMLSelectElement\n const user = userFor(element)\n // user-event adds to a multi-select's existing selection; the shared API\n // replaces it, so clear first — as the dom driver does. user-event matches\n // a value or a label, and rejects one that matches no option.\n if (element.multiple) {\n const selected = [...element.selectedOptions].map(option => option.value)\n if (selected.length > 0) await user.deselectOptions(element, selected)\n }\n await user.selectOptions(element, value)\n await settled()\n }\n\n override async hover(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n await userFor(element).hover(element)\n await settled()\n }\n\n override async focus(options?: WaitOptions): Promise<void> {\n // The DOM method: a no-op on an element that cannot take focus, where\n // test-helpers' focus() throws.\n ;(await this.element(options)).focus()\n await settled()\n }\n\n override async blur(options?: WaitOptions): Promise<void> {\n ;(await this.element(options)).blur()\n await settled()\n }\n\n override async press(key: string, options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n // Focus, then press: the key goes to the focused element, as in a browser.\n // On an element that cannot take focus, that is whatever already had it.\n element.focus()\n await userFor(element).keyboard(toKeyboardInput(key))\n await settled()\n }\n\n /** Ember's test helpers have no file upload; user-event's is the one Testing Library users know. */\n override async upload(files: File | File[], options?: WaitOptions): Promise<void> {\n const list = Array.isArray(files) ? files : [files]\n const input = fileInputOf(await this.element(options))\n checkFileTarget(input, list.length, this.scope, this.selector)\n await userFor(input).upload(input, list)\n await settled()\n }\n\n // --- observations -------------------------------------------------------\n\n override async text(options?: WaitOptions): Promise<string> {\n return ((await this.element(options)).textContent ?? '').trim()\n }\n\n override async inputValue(options?: WaitOptions): Promise<string> {\n const element = await this.element(options)\n return (element as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement).value ?? ''\n }\n\n override async attribute(name: string, options?: WaitOptions): Promise<string | null> {\n return (await this.element(options)).getAttribute(name)\n }\n\n override async isVisible(options?: WaitOptions): Promise<boolean> {\n try {\n // A real browser, so a real layout check — Playwright's rule, not jsdom's\n // computed-style walk.\n return isVisibleInLayout(await this.element(options), this.container().ownerDocument)\n } catch (error) {\n // Several matches is ambiguity, not invisibility: answering false would\n // claim that nodes on screen are not.\n if (error instanceof FrameEntryError || error instanceof StrictModeViolation) throw error\n // Not on screen at all, or an index past the last match.\n return false\n }\n }\n\n override async isEnabled(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n // The property, and an ancestor `fieldset[disabled]`: see `DomQuery.isEnabled`.\n const disabled =\n (element as HTMLInputElement).disabled === true || element.closest(':disabled') !== null\n return enabledFrom(disabled, element.getAttribute('aria-disabled'))\n }\n\n override async isChecked(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n return checkedFrom(\n element.getAttribute('aria-checked'),\n Boolean((element as HTMLInputElement).checked),\n )\n }\n\n override async selectedOptions(options?: WaitOptions): Promise<string[]> {\n const element = (await this.element(options)) as HTMLSelectElement\n return [...(element.selectedOptions ?? [])].map(option => option.value)\n }\n\n override async boundingBox(options?: WaitOptions): Promise<BoundingBox> {\n return boundingBoxOf(await this.element(options), this.container().ownerDocument)\n }\n\n // --- waiting ------------------------------------------------------------\n\n override async waitFor(state: WaitState, options?: WaitOptions): Promise<void> {\n const timeout = timeoutFor(options?.timeout)\n if (state === 'visible') {\n await waitUntil(async () => {\n if (!(await this.isVisible({ timeout }))) throw new Error('not visible yet')\n }, timeout)\n } else {\n await waitUntil(async () => {\n if ((await this.count()) !== 0 && (await this.isVisible({ timeout }))) {\n throw new Error('still visible')\n }\n }, timeout)\n }\n // Whatever made it appear or go may have scheduled more work; hand back a\n // settled app, as every interaction does.\n await settled()\n }\n\n override async count(): Promise<number> {\n return countAll(this.container(), this.scope, this.selector)\n }\n}\n\n/** A query pinned to a node `all()` already found. See `BoundDomQuery`. */\nclass BoundEmberQuery extends EmberQuery {\n constructor(\n root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n private readonly bound: HTMLElement,\n ) {\n super(root, scope, selector)\n }\n\n protected override async element(options?: WaitOptions): Promise<HTMLElement> {\n if (this.bound.isConnected) return this.bound\n return super.element(options)\n }\n}\n\nregisterDriver(EMBER_DRIVER, (env: EnvConfig, scope, selector) => {\n return new EmberQuery((env as EmberEnv).root, scope, selector)\n})\n","import { currentURL, settled, visit } from '@ember/test-helpers'\nimport { registerNavigation } from '@harnessed-ts/core'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport { EMBER_DRIVER } from './driver-id'\n\n/**\n * The router's URL, made absolute. An application test drives the router, not\n * the address bar — its location is `none` — so `currentURL()` is the truth and\n * `window.location` is not. Absolute because a page reads `pathname` and\n * `searchParams` off it with `new URL()`.\n *\n * There is not always a router URL: a rendering test has no router, and an\n * application test has none until its first visit. Reading that as `/` would\n * let `assertPathname('/')` pass before anything navigated, so it refuses.\n */\nfunction absolute(): string {\n const url = currentURL()\n if (url == null) {\n throw new Error(\n 'harnessed: the app has not visited a URL. A rendering test has no router; ' +\n 'in an application test, call visit() or a page’s goto() first.',\n )\n }\n return new URL(url, window.location.origin).href\n}\n\n/** Navigation through the router, in application tests. */\nregisterNavigation(EMBER_DRIVER, {\n async goto(_env, url) {\n // visit() resolves once the transition, and everything it scheduled, settles.\n await visit(url)\n },\n currentUrl() {\n return absolute()\n },\n async waitForUrl(_env, matches, timeout) {\n // Outside the retry loop: no router is a refusal, not something to wait out.\n absolute()\n await waitForCondition(\n () => {\n if (!matches(new URL(absolute()))) throw new Error(`still at ${absolute()}`)\n },\n { timeout },\n )\n // The URL changes before the destination has rendered.\n await settled()\n },\n})\n","import type { EnvConfig } from '@harnessed-ts/core'\nimport { EMBER_DRIVER } from './driver-id'\n// Imported for their side effects: constructing an env is the moment the driver\n// has to be in the registry, and a bare re-export could be tree-shaken away.\nimport './ember-query'\nimport './navigation'\n\nexport { EMBER_DRIVER }\n\nexport interface EmberEnv extends EnvConfig {\n readonly driver: typeof EMBER_DRIVER\n /**\n * Where queries start, scoped and `{ global: true }` alike. Defaults to the\n * test's root element (`#ember-testing`), read at query time so one env serves\n * a whole test. Not `document.body`: the test page also holds QUnit's own\n * report, whose test names would make text and role queries ambiguous.\n */\n readonly root: Element | undefined\n}\n\nexport interface EmberEnvOptions {\n root?: Element\n}\n\n/**\n * Builds the env a harness is constructed with in an Ember rendering or\n * application test:\n *\n * ```ts\n * await render(<template><LoginForm /></template>)\n * const form = new LoginFormHarness(ember())\n * ```\n */\nexport function ember({ root }: EmberEnvOptions = {}): EmberEnv {\n return { driver: EMBER_DRIVER, root }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/driver-id.ts","../src/ember-query.ts","../src/navigation.ts","../src/env.ts"],"names":["userEvent","waitForCondition","FrameEntryError","StrictModeViolation","Query","getRootElement","resolveOne","resolveAll","withNth","click","fillIn","settled","fileInputOf","checkFileTarget","isVisibleInLayout","enabledFrom","checkedFrom","boundingBoxOf","timeoutFor","countAll","registerDriver","currentURL","registerNavigation","visit"],"mappings":"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","file":"index.cjs","sourcesContent":["/** The registry key for this driver. Its own module so env and query share it without a cycle. */\nexport const EMBER_DRIVER = 'ember'\n","import { click, fillIn, getRootElement, settled } from '@ember/test-helpers'\nimport { Query, registerDriver } from '@harnessed-ts/core'\nimport type { BoundingBox, EnvConfig, Selector, WaitOptions, WaitState } from '@harnessed-ts/core'\nimport {\n checkedFrom,\n checkFileTarget,\n enabledFrom,\n nth as withNth,\n StrictModeViolation,\n timeoutFor,\n} from '@harnessed-ts/core'\nimport {\n boundingBoxOf,\n countAll,\n fileInputOf,\n FrameEntryError,\n isVisibleInLayout,\n resolveAll,\n resolveOne,\n} from '@harnessed-ts/resolve'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport userEvent from '@testing-library/user-event'\nimport type { UserEvent } from '@testing-library/user-event'\nimport { EMBER_DRIVER } from './driver-id'\nimport type { EmberEnv } from './env'\n\n/**\n * One user-event instance per document, kept so pointer state carries between\n * calls: hovering B after A has to leave A first.\n */\nconst users = new WeakMap<Document, UserEvent>()\n\nfunction userFor(element: Element): UserEvent {\n const document = element.ownerDocument\n let user = users.get(document)\n if (user === undefined) {\n user = userEvent.setup({ document })\n users.set(document, user)\n }\n return user\n}\n\n/** Playwright key names such as `Enter` map onto user-event's `{Enter}` syntax. */\nfunction toKeyboardInput(key: string): string {\n return key.length === 1 ? key : `{${key}}`\n}\n\n/** user-event's `accept` rule: case-insensitive, jpg and jpeg alike, `image/*` wildcards. */\nfunction accepts(accept: string, file: File): boolean {\n if (accept === '') return true\n const normalize = (nameOrType: string) =>\n nameOrType.toLowerCase().replace(/(\\.|\\/)jpg\\b/g, '$1jpeg')\n return normalize(accept)\n .trim()\n .split(/\\s*,\\s*/)\n .some(token => {\n if (token.startsWith('.')) return normalize(file.name).endsWith(token)\n if (['audio/*', 'image/*', 'video/*'].includes(token)) {\n return normalize(file.type).startsWith(token.slice(0, -1))\n }\n return normalize(file.type) === token\n })\n}\n\n/**\n * Testing Library's `waitFor` retries every throw, but a frame that cannot be\n * entered never becomes enterable, and several matches never become one — so\n * either ends the wait at once. Not test-helpers' `waitUntil`: its callback is\n * synchronous, and every check here resolves through the async resolver.\n */\nasync function waitUntil(condition: () => Promise<void>, timeout: number): Promise<void> {\n let refused: FrameEntryError | StrictModeViolation | undefined\n await waitForCondition(\n async () => {\n try {\n await condition()\n } catch (error) {\n if (!(error instanceof FrameEntryError || error instanceof StrictModeViolation)) {\n throw error\n }\n refused = error\n }\n },\n { timeout },\n )\n if (refused !== undefined) throw refused\n}\n\n/**\n * Ember driver. Resolution is the shared resolver, so it agrees with every other\n * in-page driver. Every interaction resolves only once the app has settled —\n * runloop drained, rendering done, test waiters clear.\n *\n * Clicks and fills go through `@ember/test-helpers`, the way an Ember test\n * dispatches them. Keys, hover, focus and selects go through user-event, as in\n * the dom driver: test-helpers' versions dispatch events without a browser's\n * default actions (a pressed key never reaches the value), refuse elements a\n * browser would accept (a disabled control, a non-focusable dialog), and match\n * options by value only.\n *\n * Each helper is handed the resolved element, never a selector string, so\n * strictness and scope stay this library's rather than `querySelector`'s.\n */\nexport class EmberQuery extends Query {\n constructor(\n private readonly root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n ) {\n super(scope, selector)\n }\n\n protected override clone(selector: Selector): Query {\n return new EmberQuery(this.root, this.scope, selector)\n }\n\n /** Read per call: the root element belongs to the current test, not the env. */\n protected container(): HTMLElement {\n return (this.root ?? getRootElement()) as HTMLElement\n }\n\n protected element(options?: WaitOptions): Promise<HTMLElement> {\n return resolveOne(this.container(), this.scope, this.selector, options?.timeout)\n }\n\n /** Every match in one pass, each bound to its node. See `DomQuery.all`. */\n override async all(): Promise<Query[]> {\n return (await resolveAll(this.container(), this.scope, this.selector)).map(\n (element, index) =>\n new BoundEmberQuery(this.root, this.scope, withNth(this.selector, index), element),\n )\n }\n\n // --- interactions -------------------------------------------------------\n\n override async click(options?: WaitOptions): Promise<void> {\n await click(await this.element(options))\n }\n\n override async fill(value: string, options?: WaitOptions): Promise<void> {\n // fillIn replaces the value and fires input and change: Playwright's fill.\n await fillIn(await this.element(options), value)\n }\n\n override async clear(options?: WaitOptions): Promise<void> {\n await fillIn(await this.element(options), '')\n }\n\n override async check(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if (!(element as HTMLInputElement).checked) await click(element)\n }\n\n override async uncheck(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if ((element as HTMLInputElement).checked) await click(element)\n }\n\n override async selectOption(value: string | string[], options?: WaitOptions): Promise<void> {\n const element = (await this.element(options)) as HTMLSelectElement\n const user = userFor(element)\n // user-event adds to a multi-select's existing selection; the shared API\n // replaces it, so clear first — as the dom driver does. user-event matches\n // a value or a label, and rejects one that matches no option.\n if (element.multiple) {\n const selected = [...element.selectedOptions].map(option => option.value)\n if (selected.length > 0) await user.deselectOptions(element, selected)\n }\n await user.selectOptions(element, value)\n await settled()\n }\n\n override async hover(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n await userFor(element).hover(element)\n await settled()\n }\n\n override async focus(options?: WaitOptions): Promise<void> {\n // The DOM method: a no-op on an element that cannot take focus, where\n // test-helpers' focus() throws.\n ;(await this.element(options)).focus()\n await settled()\n }\n\n override async blur(options?: WaitOptions): Promise<void> {\n ;(await this.element(options)).blur()\n await settled()\n }\n\n override async press(key: string, options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n // Focus, then press: the key goes to the focused element, as in a browser.\n // On an element that cannot take focus, that is whatever already had it.\n element.focus()\n await userFor(element).keyboard(toKeyboardInput(key))\n await settled()\n }\n\n /**\n * What user-event's upload does — skip a disabled input, drop files `accept`\n * refuses, fire nothing when the selection is unchanged — without user-event:\n * its setup patches the document for good, and an Ember run shares one\n * document, so a number input in a later test read \"42.50\" back as \"42.5\".\n */\n override async upload(files: File | File[], options?: WaitOptions): Promise<void> {\n const list = Array.isArray(files) ? files : [files]\n const input = fileInputOf(await this.element(options))\n checkFileTarget(input, list.length, this.scope, this.selector)\n if (input.matches(':disabled')) return\n const chosen = list.filter(file => accepts(input.accept, file))\n const current = [...(input.files ?? [])]\n if (chosen.length === current.length && chosen.every((file, i) => file === current[i])) return\n // The input's own window: a DataTransfer from another realm is refused.\n const transfer = new input.ownerDocument.defaultView!.DataTransfer()\n for (const file of chosen) transfer.items.add(file)\n input.files = transfer.files\n input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n input.dispatchEvent(new Event('change', { bubbles: true }))\n await settled()\n }\n\n // --- observations -------------------------------------------------------\n\n override async text(options?: WaitOptions): Promise<string> {\n return ((await this.element(options)).textContent ?? '').trim()\n }\n\n override async inputValue(options?: WaitOptions): Promise<string> {\n const element = await this.element(options)\n return (element as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement).value ?? ''\n }\n\n override async attribute(name: string, options?: WaitOptions): Promise<string | null> {\n return (await this.element(options)).getAttribute(name)\n }\n\n override async isVisible(options?: WaitOptions): Promise<boolean> {\n try {\n // A real browser, so a real layout check — Playwright's rule, not jsdom's\n // computed-style walk.\n return isVisibleInLayout(await this.element(options), this.container().ownerDocument)\n } catch (error) {\n // Several matches is ambiguity, not invisibility: answering false would\n // claim that nodes on screen are not.\n if (error instanceof FrameEntryError || error instanceof StrictModeViolation) throw error\n // Not on screen at all, or an index past the last match.\n return false\n }\n }\n\n override async isEnabled(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n // The property, and an ancestor `fieldset[disabled]`: see `DomQuery.isEnabled`.\n const disabled =\n (element as HTMLInputElement).disabled === true || element.closest(':disabled') !== null\n return enabledFrom(disabled, element.getAttribute('aria-disabled'))\n }\n\n override async isChecked(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n return checkedFrom(\n element.getAttribute('aria-checked'),\n Boolean((element as HTMLInputElement).checked),\n )\n }\n\n override async selectedOptions(options?: WaitOptions): Promise<string[]> {\n const element = (await this.element(options)) as HTMLSelectElement\n return [...(element.selectedOptions ?? [])].map(option => option.value)\n }\n\n override async boundingBox(options?: WaitOptions): Promise<BoundingBox> {\n return boundingBoxOf(await this.element(options), this.container().ownerDocument)\n }\n\n // --- waiting ------------------------------------------------------------\n\n override async waitFor(state: WaitState, options?: WaitOptions): Promise<void> {\n const timeout = timeoutFor(options?.timeout)\n if (state === 'visible') {\n await waitUntil(async () => {\n if (!(await this.isVisible({ timeout }))) throw new Error('not visible yet')\n }, timeout)\n } else {\n await waitUntil(async () => {\n if ((await this.count()) !== 0 && (await this.isVisible({ timeout }))) {\n throw new Error('still visible')\n }\n }, timeout)\n }\n // Whatever made it appear or go may have scheduled more work; hand back a\n // settled app, as every interaction does.\n await settled()\n }\n\n override async count(): Promise<number> {\n return countAll(this.container(), this.scope, this.selector)\n }\n}\n\n/** A query pinned to a node `all()` already found. See `BoundDomQuery`. */\nclass BoundEmberQuery extends EmberQuery {\n constructor(\n root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n private readonly bound: HTMLElement,\n ) {\n super(root, scope, selector)\n }\n\n protected override async element(options?: WaitOptions): Promise<HTMLElement> {\n if (this.bound.isConnected) return this.bound\n return super.element(options)\n }\n}\n\nregisterDriver(EMBER_DRIVER, (env: EnvConfig, scope, selector) => {\n return new EmberQuery((env as EmberEnv).root, scope, selector)\n})\n","import { currentURL, settled, visit } from '@ember/test-helpers'\nimport { registerNavigation } from '@harnessed-ts/core'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport { EMBER_DRIVER } from './driver-id'\n\n/**\n * The router's URL, made absolute. An application test drives the router, not\n * the address bar — its location is `none` — so `currentURL()` is the truth and\n * `window.location` is not. Absolute because a page reads `pathname` and\n * `searchParams` off it with `new URL()`.\n *\n * There is not always a router URL: a rendering test has no router, and an\n * application test has none until its first visit. Reading that as `/` would\n * let `assertPathname('/')` pass before anything navigated, so it refuses.\n */\nfunction absolute(): string {\n const url = currentURL()\n if (url == null) {\n throw new Error(\n 'harnessed: the app has not visited a URL. A rendering test has no router; ' +\n 'in an application test, call visit() or a page’s goto() first.',\n )\n }\n return new URL(url, window.location.origin).href\n}\n\n/** Navigation through the router, in application tests. */\nregisterNavigation(EMBER_DRIVER, {\n async goto(_env, url) {\n // visit() resolves once the transition, and everything it scheduled, settles.\n await visit(url)\n },\n currentUrl() {\n return absolute()\n },\n async waitForUrl(_env, matches, timeout) {\n // Outside the retry loop: no router is a refusal, not something to wait out.\n absolute()\n await waitForCondition(\n () => {\n if (!matches(new URL(absolute()))) throw new Error(`still at ${absolute()}`)\n },\n { timeout },\n )\n // The URL changes before the destination has rendered.\n await settled()\n },\n})\n","import type { EnvConfig } from '@harnessed-ts/core'\nimport { EMBER_DRIVER } from './driver-id'\n// Imported for their side effects: constructing an env is the moment the driver\n// has to be in the registry, and a bare re-export could be tree-shaken away.\nimport './ember-query'\nimport './navigation'\n\nexport { EMBER_DRIVER }\n\nexport interface EmberEnv extends EnvConfig {\n readonly driver: typeof EMBER_DRIVER\n /**\n * Where queries start, scoped and `{ global: true }` alike. Defaults to the\n * test's root element (`#ember-testing`), read at query time so one env serves\n * a whole test. Not `document.body`: the test page also holds QUnit's own\n * report, whose test names would make text and role queries ambiguous.\n */\n readonly root: Element | undefined\n}\n\nexport interface EmberEnvOptions {\n root?: Element\n}\n\n/**\n * Builds the env a harness is constructed with in an Ember rendering or\n * application test:\n *\n * ```ts\n * await render(<template><LoginForm /></template>)\n * const form = new LoginFormHarness(ember())\n * ```\n */\nexport function ember({ root }: EmberEnvOptions = {}): EmberEnv {\n return { driver: EMBER_DRIVER, root }\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,17 @@ function userFor(element) {
|
|
|
19
19
|
function toKeyboardInput(key) {
|
|
20
20
|
return key.length === 1 ? key : `{${key}}`;
|
|
21
21
|
}
|
|
22
|
+
function accepts(accept, file) {
|
|
23
|
+
if (accept === "") return true;
|
|
24
|
+
const normalize = (nameOrType) => nameOrType.toLowerCase().replace(/(\.|\/)jpg\b/g, "$1jpeg");
|
|
25
|
+
return normalize(accept).trim().split(/\s*,\s*/).some((token) => {
|
|
26
|
+
if (token.startsWith(".")) return normalize(file.name).endsWith(token);
|
|
27
|
+
if (["audio/*", "image/*", "video/*"].includes(token)) {
|
|
28
|
+
return normalize(file.type).startsWith(token.slice(0, -1));
|
|
29
|
+
}
|
|
30
|
+
return normalize(file.type) === token;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
22
33
|
async function waitUntil(condition, timeout) {
|
|
23
34
|
let refused;
|
|
24
35
|
await waitFor(
|
|
@@ -105,12 +116,25 @@ var EmberQuery = class _EmberQuery extends Query {
|
|
|
105
116
|
await userFor(element).keyboard(toKeyboardInput(key));
|
|
106
117
|
await settled();
|
|
107
118
|
}
|
|
108
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* What user-event's upload does — skip a disabled input, drop files `accept`
|
|
121
|
+
* refuses, fire nothing when the selection is unchanged — without user-event:
|
|
122
|
+
* its setup patches the document for good, and an Ember run shares one
|
|
123
|
+
* document, so a number input in a later test read "42.50" back as "42.5".
|
|
124
|
+
*/
|
|
109
125
|
async upload(files, options) {
|
|
110
126
|
const list = Array.isArray(files) ? files : [files];
|
|
111
127
|
const input = fileInputOf(await this.element(options));
|
|
112
128
|
checkFileTarget(input, list.length, this.scope, this.selector);
|
|
113
|
-
|
|
129
|
+
if (input.matches(":disabled")) return;
|
|
130
|
+
const chosen = list.filter((file) => accepts(input.accept, file));
|
|
131
|
+
const current = [...input.files ?? []];
|
|
132
|
+
if (chosen.length === current.length && chosen.every((file, i) => file === current[i])) return;
|
|
133
|
+
const transfer = new input.ownerDocument.defaultView.DataTransfer();
|
|
134
|
+
for (const file of chosen) transfer.items.add(file);
|
|
135
|
+
input.files = transfer.files;
|
|
136
|
+
input.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
137
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
114
138
|
await settled();
|
|
115
139
|
}
|
|
116
140
|
// --- observations -------------------------------------------------------
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/driver-id.ts","../src/ember-query.ts","../src/navigation.ts","../src/env.ts"],"names":["waitForCondition","withNth","settled"],"mappings":";;;;;;;AACO,IAAM,YAAA,GAAe;AC6B5B,IAAM,KAAA,uBAAY,OAAA,EAA6B;AAE/C,SAAS,QAAQ,OAAA,EAA6B;AAC5C,EAAA,MAAM,WAAW,OAAA,CAAQ,aAAA;AACzB,EAAA,IAAI,IAAA,GAAO,KAAA,CAAM,GAAA,CAAI,QAAQ,CAAA;AAC7B,EAAA,IAAI,SAAS,MAAA,EAAW;AACtB,IAAA,IAAA,GAAO,SAAA,CAAU,KAAA,CAAM,EAAE,QAAA,EAAU,CAAA;AACnC,IAAA,KAAA,CAAM,GAAA,CAAI,UAAU,IAAI,CAAA;AAAA,EAC1B;AACA,EAAA,OAAO,IAAA;AACT;AAGA,SAAS,gBAAgB,GAAA,EAAqB;AAC5C,EAAA,OAAO,GAAA,CAAI,MAAA,KAAW,CAAA,GAAI,GAAA,GAAM,IAAI,GAAG,CAAA,CAAA,CAAA;AACzC;AAQA,eAAe,SAAA,CAAU,WAAgC,OAAA,EAAgC;AACvF,EAAA,IAAI,OAAA;AACJ,EAAA,MAAMA,OAAA;AAAA,IACJ,YAAY;AACV,MAAA,IAAI;AACF,QAAA,MAAM,SAAA,EAAU;AAAA,MAClB,SAAS,KAAA,EAAO;AACd,QAAA,IAAI,EAAE,KAAA,YAAiB,eAAA,IAAmB,KAAA,YAAiB,mBAAA,CAAA,EAAsB;AAC/E,UAAA,MAAM,KAAA;AAAA,QACR;AACA,QAAA,OAAA,GAAU,KAAA;AAAA,MACZ;AAAA,IACF,CAAA;AAAA,IACA,EAAE,OAAA;AAAQ,GACZ;AACA,EAAA,IAAI,OAAA,KAAY,QAAW,MAAM,OAAA;AACnC;AAiBO,IAAM,UAAA,GAAN,MAAM,WAAA,SAAmB,KAAA,CAAM;AAAA,EACpC,WAAA,CACmB,IAAA,EACjB,KAAA,EACA,QAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAO,QAAQ,CAAA;AAJJ,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA,EAKnB;AAAA,EALmB,IAAA;AAAA,EAOA,MAAM,QAAA,EAA2B;AAClD,IAAA,OAAO,IAAI,WAAA,CAAW,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,OAAO,QAAQ,CAAA;AAAA,EACvD;AAAA;AAAA,EAGU,SAAA,GAAyB;AACjC,IAAA,OAAQ,IAAA,CAAK,QAAQ,cAAA,EAAe;AAAA,EACtC;AAAA,EAEU,QAAQ,OAAA,EAA6C;AAC7D,IAAA,OAAO,UAAA,CAAW,KAAK,SAAA,EAAU,EAAG,KAAK,KAAA,EAAO,IAAA,CAAK,QAAA,EAAU,OAAA,EAAS,OAAO,CAAA;AAAA,EACjF;AAAA;AAAA,EAGA,MAAe,GAAA,GAAwB;AACrC,IAAA,OAAA,CAAQ,MAAM,WAAW,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,QAAQ,CAAA,EAAG,GAAA;AAAA,MACrE,CAAC,OAAA,EAAS,KAAA,KACR,IAAI,gBAAgB,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,KAAA,EAAOC,GAAA,CAAQ,IAAA,CAAK,QAAA,EAAU,KAAK,GAAG,OAAO;AAAA,KACrF;AAAA,EACF;AAAA;AAAA,EAIA,MAAe,MAAM,OAAA,EAAsC;AACzD,IAAA,MAAM,KAAA,CAAM,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAC,CAAA;AAAA,EACzC;AAAA,EAEA,MAAe,IAAA,CAAK,KAAA,EAAe,OAAA,EAAsC;AAEvE,IAAA,MAAM,OAAO,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,KAAK,CAAA;AAAA,EACjD;AAAA,EAEA,MAAe,MAAM,OAAA,EAAsC;AACzD,IAAA,MAAM,OAAO,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,EAAE,CAAA;AAAA,EAC9C;AAAA,EAEA,MAAe,MAAM,OAAA,EAAsC;AACzD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAC1C,IAAA,IAAI,CAAE,OAAA,CAA6B,OAAA,EAAS,MAAM,MAAM,OAAO,CAAA;AAAA,EACjE;AAAA,EAEA,MAAe,QAAQ,OAAA,EAAsC;AAC3D,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAC1C,IAAA,IAAK,OAAA,CAA6B,OAAA,EAAS,MAAM,KAAA,CAAM,OAAO,CAAA;AAAA,EAChE;AAAA,EAEA,MAAe,YAAA,CAAa,KAAA,EAA0B,OAAA,EAAsC;AAC1F,IAAA,MAAM,OAAA,GAAW,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAC3C,IAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAI5B,IAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,MAAA,MAAM,QAAA,GAAW,CAAC,GAAG,OAAA,CAAQ,eAAe,CAAA,CAAE,GAAA,CAAI,CAAA,MAAA,KAAU,MAAA,CAAO,KAAK,CAAA;AACxE,MAAA,IAAI,SAAS,MAAA,GAAS,CAAA,QAAS,IAAA,CAAK,eAAA,CAAgB,SAAS,QAAQ,CAAA;AAAA,IACvE;AACA,IAAA,MAAM,IAAA,CAAK,aAAA,CAAc,OAAA,EAAS,KAAK,CAAA;AACvC,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AAAA,EAEA,MAAe,MAAM,OAAA,EAAsC;AACzD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAC1C,IAAA,MAAM,OAAA,CAAQ,OAAO,CAAA,CAAE,KAAA,CAAM,OAAO,CAAA;AACpC,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AAAA,EAEA,MAAe,MAAM,OAAA,EAAsC;AAGxD,IAAA,CAAC,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,KAAA,EAAM;AACrC,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AAAA,EAEA,MAAe,KAAK,OAAA,EAAsC;AACvD,IAAA,CAAC,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,IAAA,EAAK;AACpC,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AAAA,EAEA,MAAe,KAAA,CAAM,GAAA,EAAa,OAAA,EAAsC;AACtE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAG1C,IAAA,OAAA,CAAQ,KAAA,EAAM;AACd,IAAA,MAAM,QAAQ,OAAO,CAAA,CAAE,QAAA,CAAS,eAAA,CAAgB,GAAG,CAAC,CAAA;AACpD,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AAAA;AAAA,EAGA,MAAe,MAAA,CAAO,KAAA,EAAsB,OAAA,EAAsC;AAChF,IAAA,MAAM,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,GAAQ,CAAC,KAAK,CAAA;AAClD,IAAA,MAAM,QAAQ,WAAA,CAAY,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAC,CAAA;AACrD,IAAA,eAAA,CAAgB,OAAO,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,KAAA,EAAO,KAAK,QAAQ,CAAA;AAC7D,IAAA,MAAM,OAAA,CAAQ,KAAK,CAAA,CAAE,MAAA,CAAO,OAAO,IAAI,CAAA;AACvC,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AAAA;AAAA,EAIA,MAAe,KAAK,OAAA,EAAwC;AAC1D,IAAA,OAAA,CAAA,CAAS,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,EAAG,WAAA,IAAe,IAAI,IAAA,EAAK;AAAA,EAChE;AAAA,EAEA,MAAe,WAAW,OAAA,EAAwC;AAChE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAC1C,IAAA,OAAQ,QAAuE,KAAA,IAAS,EAAA;AAAA,EAC1F;AAAA,EAEA,MAAe,SAAA,CAAU,IAAA,EAAc,OAAA,EAA+C;AACpF,IAAA,OAAA,CAAQ,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,EAAG,aAAa,IAAI,CAAA;AAAA,EACxD;AAAA,EAEA,MAAe,UAAU,OAAA,EAAyC;AAChE,IAAA,IAAI;AAGF,MAAA,OAAO,iBAAA,CAAkB,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,EAAG,IAAA,CAAK,SAAA,EAAU,CAAE,aAAa,CAAA;AAAA,IACtF,SAAS,KAAA,EAAO;AAGd,MAAA,IAAI,KAAA,YAAiB,eAAA,IAAmB,KAAA,YAAiB,mBAAA,EAAqB,MAAM,KAAA;AAEpF,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAe,UAAU,OAAA,EAAyC;AAChE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAE1C,IAAA,MAAM,WACH,OAAA,CAA6B,QAAA,KAAa,QAAQ,OAAA,CAAQ,OAAA,CAAQ,WAAW,CAAA,KAAM,IAAA;AACtF,IAAA,OAAO,WAAA,CAAY,QAAA,EAAU,OAAA,CAAQ,YAAA,CAAa,eAAe,CAAC,CAAA;AAAA,EACpE;AAAA,EAEA,MAAe,UAAU,OAAA,EAAyC;AAChE,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAC1C,IAAA,OAAO,WAAA;AAAA,MACL,OAAA,CAAQ,aAAa,cAAc,CAAA;AAAA,MACnC,OAAA,CAAS,QAA6B,OAAO;AAAA,KAC/C;AAAA,EACF;AAAA,EAEA,MAAe,gBAAgB,OAAA,EAA0C;AACvE,IAAA,MAAM,OAAA,GAAW,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAC3C,IAAA,OAAO,CAAC,GAAI,OAAA,CAAQ,eAAA,IAAmB,EAAG,CAAA,CAAE,GAAA,CAAI,CAAA,MAAA,KAAU,MAAA,CAAO,KAAK,CAAA;AAAA,EACxE;AAAA,EAEA,MAAe,YAAY,OAAA,EAA6C;AACtE,IAAA,OAAO,aAAA,CAAc,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,EAAG,IAAA,CAAK,SAAA,EAAU,CAAE,aAAa,CAAA;AAAA,EAClF;AAAA;AAAA,EAIA,MAAe,OAAA,CAAQ,KAAA,EAAkB,OAAA,EAAsC;AAC7E,IAAA,MAAM,OAAA,GAAU,UAAA,CAAW,OAAA,EAAS,OAAO,CAAA;AAC3C,IAAA,IAAI,UAAU,SAAA,EAAW;AACvB,MAAA,MAAM,UAAU,YAAY;AAC1B,QAAA,IAAI,CAAE,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,OAAA,EAAS,CAAA,EAAI,MAAM,IAAI,KAAA,CAAM,iBAAiB,CAAA;AAAA,MAC7E,GAAG,OAAO,CAAA;AAAA,IACZ,CAAA,MAAO;AACL,MAAA,MAAM,UAAU,YAAY;AAC1B,QAAA,IAAK,MAAM,IAAA,CAAK,KAAA,EAAM,KAAO,CAAA,IAAM,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,OAAA,EAAS,CAAA,EAAI;AACrE,UAAA,MAAM,IAAI,MAAM,eAAe,CAAA;AAAA,QACjC;AAAA,MACF,GAAG,OAAO,CAAA;AAAA,IACZ;AAGA,IAAA,MAAM,OAAA,EAAQ;AAAA,EAChB;AAAA,EAEA,MAAe,KAAA,GAAyB;AACtC,IAAA,OAAO,SAAS,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,KAAA,EAAO,KAAK,QAAQ,CAAA;AAAA,EAC7D;AACF;AAGA,IAAM,eAAA,GAAN,cAA8B,UAAA,CAAW;AAAA,EACvC,WAAA,CACE,IAAA,EACA,KAAA,EACA,QAAA,EACiB,KAAA,EACjB;AACA,IAAA,KAAA,CAAM,IAAA,EAAM,OAAO,QAAQ,CAAA;AAFV,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA,EAGnB;AAAA,EAHmB,KAAA;AAAA,EAKnB,MAAyB,QAAQ,OAAA,EAA6C;AAC5E,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,WAAA,EAAa,OAAO,IAAA,CAAK,KAAA;AACxC,IAAA,OAAO,KAAA,CAAM,QAAQ,OAAO,CAAA;AAAA,EAC9B;AACF,CAAA;AAEA,cAAA,CAAe,YAAA,EAAc,CAAC,GAAA,EAAgB,KAAA,EAAO,QAAA,KAAa;AAChE,EAAA,OAAO,IAAI,UAAA,CAAY,GAAA,CAAiB,IAAA,EAAM,OAAO,QAAQ,CAAA;AAC/D,CAAC,CAAA;AClRD,SAAS,QAAA,GAAmB;AAC1B,EAAA,MAAM,MAAM,UAAA,EAAW;AACvB,EAAA,IAAI,OAAO,IAAA,EAAM;AACf,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,IAAI,GAAA,CAAI,GAAA,EAAK,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,CAAE,IAAA;AAC9C;AAGA,kBAAA,CAAmB,YAAA,EAAc;AAAA,EAC/B,MAAM,IAAA,CAAK,IAAA,EAAM,GAAA,EAAK;AAEpB,IAAA,MAAM,MAAM,GAAG,CAAA;AAAA,EACjB,CAAA;AAAA,EACA,UAAA,GAAa;AACX,IAAA,OAAO,QAAA,EAAS;AAAA,EAClB,CAAA;AAAA,EACA,MAAM,UAAA,CAAW,IAAA,EAAM,OAAA,EAAS,OAAA,EAAS;AAEvC,IAAA,QAAA,EAAS;AACT,IAAA,MAAMD,OAAAA;AAAA,MACJ,MAAM;AACJ,QAAA,IAAI,CAAC,OAAA,CAAQ,IAAI,GAAA,CAAI,UAAU,CAAC,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,CAAA,SAAA,EAAY,QAAA,EAAU,CAAA,CAAE,CAAA;AAAA,MAC7E,CAAA;AAAA,MACA,EAAE,OAAA;AAAQ,KACZ;AAEA,IAAA,MAAME,OAAAA,EAAQ;AAAA,EAChB;AACF,CAAC,CAAA;;;ACdM,SAAS,KAAA,CAAM,EAAE,IAAA,EAAK,GAAqB,EAAC,EAAa;AAC9D,EAAA,OAAO,EAAE,MAAA,EAAQ,YAAA,EAAc,IAAA,EAAK;AACtC","file":"index.js","sourcesContent":["/** The registry key for this driver. Its own module so env and query share it without a cycle. */\nexport const EMBER_DRIVER = 'ember'\n","import { click, fillIn, getRootElement, settled } from '@ember/test-helpers'\nimport { Query, registerDriver } from '@harnessed-ts/core'\nimport type { BoundingBox, EnvConfig, Selector, WaitOptions, WaitState } from '@harnessed-ts/core'\nimport {\n checkedFrom,\n checkFileTarget,\n enabledFrom,\n nth as withNth,\n StrictModeViolation,\n timeoutFor,\n} from '@harnessed-ts/core'\nimport {\n boundingBoxOf,\n countAll,\n fileInputOf,\n FrameEntryError,\n isVisibleInLayout,\n resolveAll,\n resolveOne,\n} from '@harnessed-ts/resolve'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport userEvent from '@testing-library/user-event'\nimport type { UserEvent } from '@testing-library/user-event'\nimport { EMBER_DRIVER } from './driver-id'\nimport type { EmberEnv } from './env'\n\n/**\n * One user-event instance per document, kept so pointer state carries between\n * calls: hovering B after A has to leave A first.\n */\nconst users = new WeakMap<Document, UserEvent>()\n\nfunction userFor(element: Element): UserEvent {\n const document = element.ownerDocument\n let user = users.get(document)\n if (user === undefined) {\n user = userEvent.setup({ document })\n users.set(document, user)\n }\n return user\n}\n\n/** Playwright key names such as `Enter` map onto user-event's `{Enter}` syntax. */\nfunction toKeyboardInput(key: string): string {\n return key.length === 1 ? key : `{${key}}`\n}\n\n/**\n * Testing Library's `waitFor` retries every throw, but a frame that cannot be\n * entered never becomes enterable, and several matches never become one — so\n * either ends the wait at once. Not test-helpers' `waitUntil`: its callback is\n * synchronous, and every check here resolves through the async resolver.\n */\nasync function waitUntil(condition: () => Promise<void>, timeout: number): Promise<void> {\n let refused: FrameEntryError | StrictModeViolation | undefined\n await waitForCondition(\n async () => {\n try {\n await condition()\n } catch (error) {\n if (!(error instanceof FrameEntryError || error instanceof StrictModeViolation)) {\n throw error\n }\n refused = error\n }\n },\n { timeout },\n )\n if (refused !== undefined) throw refused\n}\n\n/**\n * Ember driver. Resolution is the shared resolver, so it agrees with every other\n * in-page driver. Every interaction resolves only once the app has settled —\n * runloop drained, rendering done, test waiters clear.\n *\n * Clicks and fills go through `@ember/test-helpers`, the way an Ember test\n * dispatches them. Keys, hover, focus and selects go through user-event, as in\n * the dom driver: test-helpers' versions dispatch events without a browser's\n * default actions (a pressed key never reaches the value), refuse elements a\n * browser would accept (a disabled control, a non-focusable dialog), and match\n * options by value only.\n *\n * Each helper is handed the resolved element, never a selector string, so\n * strictness and scope stay this library's rather than `querySelector`'s.\n */\nexport class EmberQuery extends Query {\n constructor(\n private readonly root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n ) {\n super(scope, selector)\n }\n\n protected override clone(selector: Selector): Query {\n return new EmberQuery(this.root, this.scope, selector)\n }\n\n /** Read per call: the root element belongs to the current test, not the env. */\n protected container(): HTMLElement {\n return (this.root ?? getRootElement()) as HTMLElement\n }\n\n protected element(options?: WaitOptions): Promise<HTMLElement> {\n return resolveOne(this.container(), this.scope, this.selector, options?.timeout)\n }\n\n /** Every match in one pass, each bound to its node. See `DomQuery.all`. */\n override async all(): Promise<Query[]> {\n return (await resolveAll(this.container(), this.scope, this.selector)).map(\n (element, index) =>\n new BoundEmberQuery(this.root, this.scope, withNth(this.selector, index), element),\n )\n }\n\n // --- interactions -------------------------------------------------------\n\n override async click(options?: WaitOptions): Promise<void> {\n await click(await this.element(options))\n }\n\n override async fill(value: string, options?: WaitOptions): Promise<void> {\n // fillIn replaces the value and fires input and change: Playwright's fill.\n await fillIn(await this.element(options), value)\n }\n\n override async clear(options?: WaitOptions): Promise<void> {\n await fillIn(await this.element(options), '')\n }\n\n override async check(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if (!(element as HTMLInputElement).checked) await click(element)\n }\n\n override async uncheck(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if ((element as HTMLInputElement).checked) await click(element)\n }\n\n override async selectOption(value: string | string[], options?: WaitOptions): Promise<void> {\n const element = (await this.element(options)) as HTMLSelectElement\n const user = userFor(element)\n // user-event adds to a multi-select's existing selection; the shared API\n // replaces it, so clear first — as the dom driver does. user-event matches\n // a value or a label, and rejects one that matches no option.\n if (element.multiple) {\n const selected = [...element.selectedOptions].map(option => option.value)\n if (selected.length > 0) await user.deselectOptions(element, selected)\n }\n await user.selectOptions(element, value)\n await settled()\n }\n\n override async hover(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n await userFor(element).hover(element)\n await settled()\n }\n\n override async focus(options?: WaitOptions): Promise<void> {\n // The DOM method: a no-op on an element that cannot take focus, where\n // test-helpers' focus() throws.\n ;(await this.element(options)).focus()\n await settled()\n }\n\n override async blur(options?: WaitOptions): Promise<void> {\n ;(await this.element(options)).blur()\n await settled()\n }\n\n override async press(key: string, options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n // Focus, then press: the key goes to the focused element, as in a browser.\n // On an element that cannot take focus, that is whatever already had it.\n element.focus()\n await userFor(element).keyboard(toKeyboardInput(key))\n await settled()\n }\n\n /** Ember's test helpers have no file upload; user-event's is the one Testing Library users know. */\n override async upload(files: File | File[], options?: WaitOptions): Promise<void> {\n const list = Array.isArray(files) ? files : [files]\n const input = fileInputOf(await this.element(options))\n checkFileTarget(input, list.length, this.scope, this.selector)\n await userFor(input).upload(input, list)\n await settled()\n }\n\n // --- observations -------------------------------------------------------\n\n override async text(options?: WaitOptions): Promise<string> {\n return ((await this.element(options)).textContent ?? '').trim()\n }\n\n override async inputValue(options?: WaitOptions): Promise<string> {\n const element = await this.element(options)\n return (element as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement).value ?? ''\n }\n\n override async attribute(name: string, options?: WaitOptions): Promise<string | null> {\n return (await this.element(options)).getAttribute(name)\n }\n\n override async isVisible(options?: WaitOptions): Promise<boolean> {\n try {\n // A real browser, so a real layout check — Playwright's rule, not jsdom's\n // computed-style walk.\n return isVisibleInLayout(await this.element(options), this.container().ownerDocument)\n } catch (error) {\n // Several matches is ambiguity, not invisibility: answering false would\n // claim that nodes on screen are not.\n if (error instanceof FrameEntryError || error instanceof StrictModeViolation) throw error\n // Not on screen at all, or an index past the last match.\n return false\n }\n }\n\n override async isEnabled(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n // The property, and an ancestor `fieldset[disabled]`: see `DomQuery.isEnabled`.\n const disabled =\n (element as HTMLInputElement).disabled === true || element.closest(':disabled') !== null\n return enabledFrom(disabled, element.getAttribute('aria-disabled'))\n }\n\n override async isChecked(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n return checkedFrom(\n element.getAttribute('aria-checked'),\n Boolean((element as HTMLInputElement).checked),\n )\n }\n\n override async selectedOptions(options?: WaitOptions): Promise<string[]> {\n const element = (await this.element(options)) as HTMLSelectElement\n return [...(element.selectedOptions ?? [])].map(option => option.value)\n }\n\n override async boundingBox(options?: WaitOptions): Promise<BoundingBox> {\n return boundingBoxOf(await this.element(options), this.container().ownerDocument)\n }\n\n // --- waiting ------------------------------------------------------------\n\n override async waitFor(state: WaitState, options?: WaitOptions): Promise<void> {\n const timeout = timeoutFor(options?.timeout)\n if (state === 'visible') {\n await waitUntil(async () => {\n if (!(await this.isVisible({ timeout }))) throw new Error('not visible yet')\n }, timeout)\n } else {\n await waitUntil(async () => {\n if ((await this.count()) !== 0 && (await this.isVisible({ timeout }))) {\n throw new Error('still visible')\n }\n }, timeout)\n }\n // Whatever made it appear or go may have scheduled more work; hand back a\n // settled app, as every interaction does.\n await settled()\n }\n\n override async count(): Promise<number> {\n return countAll(this.container(), this.scope, this.selector)\n }\n}\n\n/** A query pinned to a node `all()` already found. See `BoundDomQuery`. */\nclass BoundEmberQuery extends EmberQuery {\n constructor(\n root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n private readonly bound: HTMLElement,\n ) {\n super(root, scope, selector)\n }\n\n protected override async element(options?: WaitOptions): Promise<HTMLElement> {\n if (this.bound.isConnected) return this.bound\n return super.element(options)\n }\n}\n\nregisterDriver(EMBER_DRIVER, (env: EnvConfig, scope, selector) => {\n return new EmberQuery((env as EmberEnv).root, scope, selector)\n})\n","import { currentURL, settled, visit } from '@ember/test-helpers'\nimport { registerNavigation } from '@harnessed-ts/core'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport { EMBER_DRIVER } from './driver-id'\n\n/**\n * The router's URL, made absolute. An application test drives the router, not\n * the address bar — its location is `none` — so `currentURL()` is the truth and\n * `window.location` is not. Absolute because a page reads `pathname` and\n * `searchParams` off it with `new URL()`.\n *\n * There is not always a router URL: a rendering test has no router, and an\n * application test has none until its first visit. Reading that as `/` would\n * let `assertPathname('/')` pass before anything navigated, so it refuses.\n */\nfunction absolute(): string {\n const url = currentURL()\n if (url == null) {\n throw new Error(\n 'harnessed: the app has not visited a URL. A rendering test has no router; ' +\n 'in an application test, call visit() or a page’s goto() first.',\n )\n }\n return new URL(url, window.location.origin).href\n}\n\n/** Navigation through the router, in application tests. */\nregisterNavigation(EMBER_DRIVER, {\n async goto(_env, url) {\n // visit() resolves once the transition, and everything it scheduled, settles.\n await visit(url)\n },\n currentUrl() {\n return absolute()\n },\n async waitForUrl(_env, matches, timeout) {\n // Outside the retry loop: no router is a refusal, not something to wait out.\n absolute()\n await waitForCondition(\n () => {\n if (!matches(new URL(absolute()))) throw new Error(`still at ${absolute()}`)\n },\n { timeout },\n )\n // The URL changes before the destination has rendered.\n await settled()\n },\n})\n","import type { EnvConfig } from '@harnessed-ts/core'\nimport { EMBER_DRIVER } from './driver-id'\n// Imported for their side effects: constructing an env is the moment the driver\n// has to be in the registry, and a bare re-export could be tree-shaken away.\nimport './ember-query'\nimport './navigation'\n\nexport { EMBER_DRIVER }\n\nexport interface EmberEnv extends EnvConfig {\n readonly driver: typeof EMBER_DRIVER\n /**\n * Where queries start, scoped and `{ global: true }` alike. Defaults to the\n * test's root element (`#ember-testing`), read at query time so one env serves\n * a whole test. Not `document.body`: the test page also holds QUnit's own\n * report, whose test names would make text and role queries ambiguous.\n */\n readonly root: Element | undefined\n}\n\nexport interface EmberEnvOptions {\n root?: Element\n}\n\n/**\n * Builds the env a harness is constructed with in an Ember rendering or\n * application test:\n *\n * ```ts\n * await render(<template><LoginForm /></template>)\n * const form = new LoginFormHarness(ember())\n * ```\n */\nexport function ember({ root }: EmberEnvOptions = {}): EmberEnv {\n return { driver: EMBER_DRIVER, root }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/driver-id.ts","../src/ember-query.ts","../src/navigation.ts","../src/env.ts"],"names":["waitForCondition","withNth","settled"],"mappings":"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","file":"index.js","sourcesContent":["/** The registry key for this driver. Its own module so env and query share it without a cycle. */\nexport const EMBER_DRIVER = 'ember'\n","import { click, fillIn, getRootElement, settled } from '@ember/test-helpers'\nimport { Query, registerDriver } from '@harnessed-ts/core'\nimport type { BoundingBox, EnvConfig, Selector, WaitOptions, WaitState } from '@harnessed-ts/core'\nimport {\n checkedFrom,\n checkFileTarget,\n enabledFrom,\n nth as withNth,\n StrictModeViolation,\n timeoutFor,\n} from '@harnessed-ts/core'\nimport {\n boundingBoxOf,\n countAll,\n fileInputOf,\n FrameEntryError,\n isVisibleInLayout,\n resolveAll,\n resolveOne,\n} from '@harnessed-ts/resolve'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport userEvent from '@testing-library/user-event'\nimport type { UserEvent } from '@testing-library/user-event'\nimport { EMBER_DRIVER } from './driver-id'\nimport type { EmberEnv } from './env'\n\n/**\n * One user-event instance per document, kept so pointer state carries between\n * calls: hovering B after A has to leave A first.\n */\nconst users = new WeakMap<Document, UserEvent>()\n\nfunction userFor(element: Element): UserEvent {\n const document = element.ownerDocument\n let user = users.get(document)\n if (user === undefined) {\n user = userEvent.setup({ document })\n users.set(document, user)\n }\n return user\n}\n\n/** Playwright key names such as `Enter` map onto user-event's `{Enter}` syntax. */\nfunction toKeyboardInput(key: string): string {\n return key.length === 1 ? key : `{${key}}`\n}\n\n/** user-event's `accept` rule: case-insensitive, jpg and jpeg alike, `image/*` wildcards. */\nfunction accepts(accept: string, file: File): boolean {\n if (accept === '') return true\n const normalize = (nameOrType: string) =>\n nameOrType.toLowerCase().replace(/(\\.|\\/)jpg\\b/g, '$1jpeg')\n return normalize(accept)\n .trim()\n .split(/\\s*,\\s*/)\n .some(token => {\n if (token.startsWith('.')) return normalize(file.name).endsWith(token)\n if (['audio/*', 'image/*', 'video/*'].includes(token)) {\n return normalize(file.type).startsWith(token.slice(0, -1))\n }\n return normalize(file.type) === token\n })\n}\n\n/**\n * Testing Library's `waitFor` retries every throw, but a frame that cannot be\n * entered never becomes enterable, and several matches never become one — so\n * either ends the wait at once. Not test-helpers' `waitUntil`: its callback is\n * synchronous, and every check here resolves through the async resolver.\n */\nasync function waitUntil(condition: () => Promise<void>, timeout: number): Promise<void> {\n let refused: FrameEntryError | StrictModeViolation | undefined\n await waitForCondition(\n async () => {\n try {\n await condition()\n } catch (error) {\n if (!(error instanceof FrameEntryError || error instanceof StrictModeViolation)) {\n throw error\n }\n refused = error\n }\n },\n { timeout },\n )\n if (refused !== undefined) throw refused\n}\n\n/**\n * Ember driver. Resolution is the shared resolver, so it agrees with every other\n * in-page driver. Every interaction resolves only once the app has settled —\n * runloop drained, rendering done, test waiters clear.\n *\n * Clicks and fills go through `@ember/test-helpers`, the way an Ember test\n * dispatches them. Keys, hover, focus and selects go through user-event, as in\n * the dom driver: test-helpers' versions dispatch events without a browser's\n * default actions (a pressed key never reaches the value), refuse elements a\n * browser would accept (a disabled control, a non-focusable dialog), and match\n * options by value only.\n *\n * Each helper is handed the resolved element, never a selector string, so\n * strictness and scope stay this library's rather than `querySelector`'s.\n */\nexport class EmberQuery extends Query {\n constructor(\n private readonly root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n ) {\n super(scope, selector)\n }\n\n protected override clone(selector: Selector): Query {\n return new EmberQuery(this.root, this.scope, selector)\n }\n\n /** Read per call: the root element belongs to the current test, not the env. */\n protected container(): HTMLElement {\n return (this.root ?? getRootElement()) as HTMLElement\n }\n\n protected element(options?: WaitOptions): Promise<HTMLElement> {\n return resolveOne(this.container(), this.scope, this.selector, options?.timeout)\n }\n\n /** Every match in one pass, each bound to its node. See `DomQuery.all`. */\n override async all(): Promise<Query[]> {\n return (await resolveAll(this.container(), this.scope, this.selector)).map(\n (element, index) =>\n new BoundEmberQuery(this.root, this.scope, withNth(this.selector, index), element),\n )\n }\n\n // --- interactions -------------------------------------------------------\n\n override async click(options?: WaitOptions): Promise<void> {\n await click(await this.element(options))\n }\n\n override async fill(value: string, options?: WaitOptions): Promise<void> {\n // fillIn replaces the value and fires input and change: Playwright's fill.\n await fillIn(await this.element(options), value)\n }\n\n override async clear(options?: WaitOptions): Promise<void> {\n await fillIn(await this.element(options), '')\n }\n\n override async check(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if (!(element as HTMLInputElement).checked) await click(element)\n }\n\n override async uncheck(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n if ((element as HTMLInputElement).checked) await click(element)\n }\n\n override async selectOption(value: string | string[], options?: WaitOptions): Promise<void> {\n const element = (await this.element(options)) as HTMLSelectElement\n const user = userFor(element)\n // user-event adds to a multi-select's existing selection; the shared API\n // replaces it, so clear first — as the dom driver does. user-event matches\n // a value or a label, and rejects one that matches no option.\n if (element.multiple) {\n const selected = [...element.selectedOptions].map(option => option.value)\n if (selected.length > 0) await user.deselectOptions(element, selected)\n }\n await user.selectOptions(element, value)\n await settled()\n }\n\n override async hover(options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n await userFor(element).hover(element)\n await settled()\n }\n\n override async focus(options?: WaitOptions): Promise<void> {\n // The DOM method: a no-op on an element that cannot take focus, where\n // test-helpers' focus() throws.\n ;(await this.element(options)).focus()\n await settled()\n }\n\n override async blur(options?: WaitOptions): Promise<void> {\n ;(await this.element(options)).blur()\n await settled()\n }\n\n override async press(key: string, options?: WaitOptions): Promise<void> {\n const element = await this.element(options)\n // Focus, then press: the key goes to the focused element, as in a browser.\n // On an element that cannot take focus, that is whatever already had it.\n element.focus()\n await userFor(element).keyboard(toKeyboardInput(key))\n await settled()\n }\n\n /**\n * What user-event's upload does — skip a disabled input, drop files `accept`\n * refuses, fire nothing when the selection is unchanged — without user-event:\n * its setup patches the document for good, and an Ember run shares one\n * document, so a number input in a later test read \"42.50\" back as \"42.5\".\n */\n override async upload(files: File | File[], options?: WaitOptions): Promise<void> {\n const list = Array.isArray(files) ? files : [files]\n const input = fileInputOf(await this.element(options))\n checkFileTarget(input, list.length, this.scope, this.selector)\n if (input.matches(':disabled')) return\n const chosen = list.filter(file => accepts(input.accept, file))\n const current = [...(input.files ?? [])]\n if (chosen.length === current.length && chosen.every((file, i) => file === current[i])) return\n // The input's own window: a DataTransfer from another realm is refused.\n const transfer = new input.ownerDocument.defaultView!.DataTransfer()\n for (const file of chosen) transfer.items.add(file)\n input.files = transfer.files\n input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))\n input.dispatchEvent(new Event('change', { bubbles: true }))\n await settled()\n }\n\n // --- observations -------------------------------------------------------\n\n override async text(options?: WaitOptions): Promise<string> {\n return ((await this.element(options)).textContent ?? '').trim()\n }\n\n override async inputValue(options?: WaitOptions): Promise<string> {\n const element = await this.element(options)\n return (element as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement).value ?? ''\n }\n\n override async attribute(name: string, options?: WaitOptions): Promise<string | null> {\n return (await this.element(options)).getAttribute(name)\n }\n\n override async isVisible(options?: WaitOptions): Promise<boolean> {\n try {\n // A real browser, so a real layout check — Playwright's rule, not jsdom's\n // computed-style walk.\n return isVisibleInLayout(await this.element(options), this.container().ownerDocument)\n } catch (error) {\n // Several matches is ambiguity, not invisibility: answering false would\n // claim that nodes on screen are not.\n if (error instanceof FrameEntryError || error instanceof StrictModeViolation) throw error\n // Not on screen at all, or an index past the last match.\n return false\n }\n }\n\n override async isEnabled(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n // The property, and an ancestor `fieldset[disabled]`: see `DomQuery.isEnabled`.\n const disabled =\n (element as HTMLInputElement).disabled === true || element.closest(':disabled') !== null\n return enabledFrom(disabled, element.getAttribute('aria-disabled'))\n }\n\n override async isChecked(options?: WaitOptions): Promise<boolean> {\n const element = await this.element(options)\n return checkedFrom(\n element.getAttribute('aria-checked'),\n Boolean((element as HTMLInputElement).checked),\n )\n }\n\n override async selectedOptions(options?: WaitOptions): Promise<string[]> {\n const element = (await this.element(options)) as HTMLSelectElement\n return [...(element.selectedOptions ?? [])].map(option => option.value)\n }\n\n override async boundingBox(options?: WaitOptions): Promise<BoundingBox> {\n return boundingBoxOf(await this.element(options), this.container().ownerDocument)\n }\n\n // --- waiting ------------------------------------------------------------\n\n override async waitFor(state: WaitState, options?: WaitOptions): Promise<void> {\n const timeout = timeoutFor(options?.timeout)\n if (state === 'visible') {\n await waitUntil(async () => {\n if (!(await this.isVisible({ timeout }))) throw new Error('not visible yet')\n }, timeout)\n } else {\n await waitUntil(async () => {\n if ((await this.count()) !== 0 && (await this.isVisible({ timeout }))) {\n throw new Error('still visible')\n }\n }, timeout)\n }\n // Whatever made it appear or go may have scheduled more work; hand back a\n // settled app, as every interaction does.\n await settled()\n }\n\n override async count(): Promise<number> {\n return countAll(this.container(), this.scope, this.selector)\n }\n}\n\n/** A query pinned to a node `all()` already found. See `BoundDomQuery`. */\nclass BoundEmberQuery extends EmberQuery {\n constructor(\n root: Element | undefined,\n scope: readonly Selector[],\n selector: Selector,\n private readonly bound: HTMLElement,\n ) {\n super(root, scope, selector)\n }\n\n protected override async element(options?: WaitOptions): Promise<HTMLElement> {\n if (this.bound.isConnected) return this.bound\n return super.element(options)\n }\n}\n\nregisterDriver(EMBER_DRIVER, (env: EnvConfig, scope, selector) => {\n return new EmberQuery((env as EmberEnv).root, scope, selector)\n})\n","import { currentURL, settled, visit } from '@ember/test-helpers'\nimport { registerNavigation } from '@harnessed-ts/core'\nimport { waitFor as waitForCondition } from '@testing-library/dom'\nimport { EMBER_DRIVER } from './driver-id'\n\n/**\n * The router's URL, made absolute. An application test drives the router, not\n * the address bar — its location is `none` — so `currentURL()` is the truth and\n * `window.location` is not. Absolute because a page reads `pathname` and\n * `searchParams` off it with `new URL()`.\n *\n * There is not always a router URL: a rendering test has no router, and an\n * application test has none until its first visit. Reading that as `/` would\n * let `assertPathname('/')` pass before anything navigated, so it refuses.\n */\nfunction absolute(): string {\n const url = currentURL()\n if (url == null) {\n throw new Error(\n 'harnessed: the app has not visited a URL. A rendering test has no router; ' +\n 'in an application test, call visit() or a page’s goto() first.',\n )\n }\n return new URL(url, window.location.origin).href\n}\n\n/** Navigation through the router, in application tests. */\nregisterNavigation(EMBER_DRIVER, {\n async goto(_env, url) {\n // visit() resolves once the transition, and everything it scheduled, settles.\n await visit(url)\n },\n currentUrl() {\n return absolute()\n },\n async waitForUrl(_env, matches, timeout) {\n // Outside the retry loop: no router is a refusal, not something to wait out.\n absolute()\n await waitForCondition(\n () => {\n if (!matches(new URL(absolute()))) throw new Error(`still at ${absolute()}`)\n },\n { timeout },\n )\n // The URL changes before the destination has rendered.\n await settled()\n },\n})\n","import type { EnvConfig } from '@harnessed-ts/core'\nimport { EMBER_DRIVER } from './driver-id'\n// Imported for their side effects: constructing an env is the moment the driver\n// has to be in the registry, and a bare re-export could be tree-shaken away.\nimport './ember-query'\nimport './navigation'\n\nexport { EMBER_DRIVER }\n\nexport interface EmberEnv extends EnvConfig {\n readonly driver: typeof EMBER_DRIVER\n /**\n * Where queries start, scoped and `{ global: true }` alike. Defaults to the\n * test's root element (`#ember-testing`), read at query time so one env serves\n * a whole test. Not `document.body`: the test page also holds QUnit's own\n * report, whose test names would make text and role queries ambiguous.\n */\n readonly root: Element | undefined\n}\n\nexport interface EmberEnvOptions {\n root?: Element\n}\n\n/**\n * Builds the env a harness is constructed with in an Ember rendering or\n * application test:\n *\n * ```ts\n * await render(<template><LoginForm /></template>)\n * const form = new LoginFormHarness(ember())\n * ```\n */\nexport function ember({ root }: EmberEnvOptions = {}): EmberEnv {\n return { driver: EMBER_DRIVER, root }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harnessed-ts/ember",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Ember driver for @harnessed-ts/core: interactions through @ember/test-helpers, settled after every one. Rendering and application tests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"testing",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"node": ">=22.12.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@harnessed-ts/core": "^0.
|
|
53
|
-
"@harnessed-ts/resolve": "^0.
|
|
52
|
+
"@harnessed-ts/core": "^0.7.0",
|
|
53
|
+
"@harnessed-ts/resolve": "^0.7.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@ember/test-helpers": ">=5",
|