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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/v4/migration/Accordion/Accordion.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterEach, describe, expect, it } from 'vitest';
import { registerComponent } from '../../src/index.js';
import { getInstance, resetDom, settle } from '../../src/test-utils.js';
import { getInstance } from '../../src/test-utils.js';
import { resetDom, settle } from '../../src/test/index.js';
import { Accordion } from './Accordion.js';
import { AccordionItem } from './AccordionItem.js';

Expand Down
103 changes: 48 additions & 55 deletions packages/v4/migration/Action/Action.spec.ts

Large diffs are not rendered by default.

49 changes: 16 additions & 33 deletions packages/v4/migration/AnchorNav/AnchorNav.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterEach, describe, expect, it } from 'vitest';
import { registerComponents } from '../../src/index.js';
import { getInstance, resetDom, settle } from '../../src/test-utils.js';
import { getInstance } from '../../src/test-utils.js';
import { mount, resetDom, settle, waitFor } from '../../src/test/index.js';
import { AnchorNav } from './AnchorNav.js';
import { AnchorNavLink } from './AnchorNavLink.js';
import { AnchorNavTarget } from './AnchorNavTarget.js';
Expand All @@ -12,37 +13,12 @@ registerComponents(AnchorNav, AnchorNavLink, AnchorNavTarget);

afterEach(resetDom);

async function observed(): Promise<void> {
for (let i = 0; i < 6; i += 1) {
await settle();
}
}

/**
* `AnchorNav` fire-and-forgets the link's transition, and a kept end state
* lands a few frames after that — so the class is polled rather than assumed
* present once the observer has delivered. `MenuList`'s spec has the same
* helper for the same reason, after this shape flaked under full-suite load.
*/
async function waitForClass(el: HTMLElement, className: string, timeout = 1000): Promise<void> {
const deadline = Date.now() + timeout;
while (!el.classList.contains(className)) {
if (Date.now() > deadline) {
throw new Error(`waitForClass: "${className}" never landed on the element`);
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
}

async function render(): Promise<{ root: HTMLElement; target: HTMLElement }> {
const root = document.createElement('div');
root.innerHTML = `
const root = await mount(`
<div data-component="AnchorNav">
<a data-component="AnchorNavLink" href="#one" data-option-enter-to="active" data-option-enter-keep="true"></a>
<div id="one" data-component="AnchorNavTarget" style="${OFFSCREEN}"></div>
</div>`;
document.body.append(root);
await settle();
</div>`);
return { root, target: root.querySelector('#one') as HTMLElement };
}

Expand All @@ -55,10 +31,12 @@ describe('AnchorNav', () => {
);

target.setAttribute('style', ONSCREEN);
await observed();
await waitFor(() => link.state === 'entering');

expect(link.state).toBe('entering');
await waitForClass(link.$el, 'active');
// `AnchorNav` fire-and-forgets the transition, so the kept end state lands
// a few frames after the state change and has to be polled for.
await waitFor(() => link.$el.classList.contains('active'));
});

it('leaves the matching link once its target scrolls back out of view', async () => {
Expand All @@ -69,11 +47,13 @@ describe('AnchorNav', () => {
);

target.setAttribute('style', ONSCREEN);
await observed();
await waitFor(() => link.state === 'entering');
target.setAttribute('style', OFFSCREEN);
await observed();
await waitFor(() => link.state === 'leaving');

expect(link.state).toBe('leaving');
// A removal is asserted directly, never polled for: `leaveTransition()`
// clears the other direction's class before its first await.
expect(link.$el.classList.contains('active')).toBe(false);
});

Expand All @@ -93,7 +73,10 @@ describe('AnchorNav', () => {
const target = root.querySelector('#one') as HTMLElement;

target.setAttribute('style', ONSCREEN);
await observed();
// An absence cannot be polled for, so this keeps a bounded quiet period.
for (let i = 0; i < 6; i += 1) {
await settle();
}

expect(link.state).toBeNull();
});
Expand Down
3 changes: 2 additions & 1 deletion packages/v4/migration/AnchorNav/AnchorNavLink.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterEach, describe, expect, it } from 'vitest';
import { registerComponents } from '../../src/index.js';
import { getInstance, resetDom, settle } from '../../src/test-utils.js';
import { getInstance } from '../../src/test-utils.js';
import { resetDom, settle } from '../../src/test/index.js';
import { AnchorNavLink } from './AnchorNavLink.js';

registerComponents(AnchorNavLink);
Expand Down
23 changes: 13 additions & 10 deletions packages/v4/migration/AnchorNav/AnchorNavTarget.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterEach, describe, expect, it } from 'vitest';
import { registerComponents } from '../../src/index.js';
import { INSTANCES } from '../../src/protocol-symbols.js';
import { resetDom, settle } from '../../src/test-utils.js';
import { resetDom, settle, waitFor } from '../../src/test/index.js';
import { AnchorNavTarget } from './AnchorNavTarget.js';

const OFFSCREEN = 'position:absolute;top:300vh;left:0;width:50px;height:50px';
Expand All @@ -11,12 +11,15 @@ registerComponents(AnchorNavTarget);

afterEach(resetDom);

async function observed(): Promise<void> {
/** A bounded quiet period, for the assertion that nothing has mounted yet. */
async function quiet(): Promise<void> {
for (let i = 0; i < 6; i += 1) {
await settle();
}
}

const mountedState = (el: HTMLElement) => el[INSTANCES]?.get('AnchorNavTarget')?.$isMounted;

function render(style: string): HTMLElement {
const el = document.createElement('div');
el.setAttribute('data-component', 'AnchorNavTarget');
Expand All @@ -28,21 +31,21 @@ function render(style: string): HTMLElement {
describe('AnchorNavTarget', () => {
it('mounts once scrolled into view', async () => {
const el = render(OFFSCREEN);
await observed();
expect(el[INSTANCES]?.get('AnchorNavTarget')?.$isMounted).toBeUndefined();
await quiet();
expect(mountedState(el)).toBeUndefined();

el.setAttribute('style', ONSCREEN);
await observed();
expect(el[INSTANCES]?.get('AnchorNavTarget')?.$isMounted).toBe(true);
await waitFor(() => mountedState(el));
expect(mountedState(el)).toBe(true);
});

it('unmounts once scrolled back out of view', async () => {
const el = render(ONSCREEN);
await observed();
expect(el[INSTANCES]?.get('AnchorNavTarget')?.$isMounted).toBe(true);
await waitFor(() => mountedState(el));
expect(mountedState(el)).toBe(true);

el.setAttribute('style', OFFSCREEN);
await observed();
expect(el[INSTANCES]?.get('AnchorNavTarget')?.$isMounted).toBe(false);
await waitFor(() => mountedState(el) === false);
expect(mountedState(el)).toBe(false);
});
});
Loading
Loading