From 657abce60c9eb4ef93d15bc16c14973c6500529c Mon Sep 17 00:00:00 2001
From: Titouan Mathis
Date: Mon, 24 Aug 2026 17:47:40 +0200
Subject: [PATCH 1/8] refactor(v4): let getInstance take a nullable element
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`getInstance()` read `el[INSTANCES]?.get(name)`, a body that already
tolerated a missing element, behind an `el: Element` parameter that did
not. Every caller holding a `querySelector()` result had to assert the
element away with a `!` — 58 of them across the specs — to state
something the function never needed.
The parameter is now `Element | null`. "No element" and "no instance"
are the same answer to the caller, and `undefined` says it for both.
The return stays `T | undefined`: an absent element is a fact the caller
may reasonably not know, an absent instance is one it must handle.
Co-Authored-By: Claude Opus 5
Claude-Session: https://claude.ai/code/session_01NnepYhqjPMcFCSb43PoHRM
---
packages/v4/migration/Action/Action.spec.ts | 2 +-
.../v4/migration/AnchorNav/AnchorNav.spec.ts | 6 +--
.../migration/AnchorNav/AnchorNavLink.spec.ts | 2 +-
.../ClickOutside/ClickOutside.spec.ts | 2 +-
packages/v4/migration/Data/DataBind.spec.ts | 2 +-
.../v4/migration/Data/DataDerived.spec.ts | 2 +-
packages/v4/migration/Data/DataScope.spec.ts | 2 +-
.../v4/migration/Figure/FigureShopify.spec.ts | 2 +-
.../migration/Figure/FigureTwicpics.spec.ts | 2 +-
.../FigureVideo/FigureVideoTwicpics.spec.ts | 2 +-
packages/v4/migration/Menu/Menu.spec.ts | 6 +--
packages/v4/migration/Menu/MenuList.spec.ts | 4 +-
packages/v4/migration/Toaster/Toaster.spec.ts | 2 +-
packages/v4/src/Base.spec.ts | 10 ++--
packages/v4/src/context-subscription.spec.ts | 4 +-
packages/v4/src/context.spec.ts | 2 +-
packages/v4/src/decorators.spec.ts | 14 +++---
packages/v4/src/group.spec.ts | 4 +-
packages/v4/src/instances.ts | 19 +++++++-
packages/v4/src/props.spec.ts | 2 +-
packages/v4/src/responsive-options.spec.ts | 46 +++++++++----------
21 files changed, 75 insertions(+), 62 deletions(-)
diff --git a/packages/v4/migration/Action/Action.spec.ts b/packages/v4/migration/Action/Action.spec.ts
index ab7e5e2b..fabe008a 100644
--- a/packages/v4/migration/Action/Action.spec.ts
+++ b/packages/v4/migration/Action/Action.spec.ts
@@ -43,7 +43,7 @@ registerComponents(Action, Target, Foo, Bar, Dialog, MountProbe);
afterEach(resetDom);
function at(root: ParentNode, selector: string, name: string): T {
- return getInstance(root.querySelector(selector)!, name)!;
+ return getInstance(root.querySelector(selector), name)!;
}
function click(el: Element): Event {
diff --git a/packages/v4/migration/AnchorNav/AnchorNav.spec.ts b/packages/v4/migration/AnchorNav/AnchorNav.spec.ts
index dbf14bef..61e40606 100644
--- a/packages/v4/migration/AnchorNav/AnchorNav.spec.ts
+++ b/packages/v4/migration/AnchorNav/AnchorNav.spec.ts
@@ -25,7 +25,7 @@ describe('AnchorNav', () => {
it('enters the matching link once its target scrolls into view', async () => {
const { root, target } = await render();
const link = getInstance(
- root.querySelector('[data-component="AnchorNavLink"]')!,
+ root.querySelector('[data-component="AnchorNavLink"]'),
'AnchorNavLink',
)!;
@@ -41,7 +41,7 @@ describe('AnchorNav', () => {
it('leaves the matching link once its target scrolls back out of view', async () => {
const { root, target } = await render();
const link = getInstance(
- root.querySelector('[data-component="AnchorNavLink"]')!,
+ root.querySelector('[data-component="AnchorNavLink"]'),
'AnchorNavLink',
)!;
@@ -66,7 +66,7 @@ describe('AnchorNav', () => {
document.body.append(root);
await settle();
const link = getInstance(
- root.querySelector('[data-component="AnchorNavLink"]')!,
+ root.querySelector('[data-component="AnchorNavLink"]'),
'AnchorNavLink',
)!;
const target = root.querySelector('#one') as HTMLElement;
diff --git a/packages/v4/migration/AnchorNav/AnchorNavLink.spec.ts b/packages/v4/migration/AnchorNav/AnchorNavLink.spec.ts
index fbfbee04..08a0f8cb 100644
--- a/packages/v4/migration/AnchorNav/AnchorNavLink.spec.ts
+++ b/packages/v4/migration/AnchorNav/AnchorNavLink.spec.ts
@@ -23,7 +23,7 @@ async function render(): Promise {
root.innerHTML = ``;
document.body.append(root);
await settle();
- return getInstance(root.firstElementChild!, 'AnchorNavLink')!;
+ return getInstance(root.firstElementChild, 'AnchorNavLink')!;
}
describe('AnchorNavLink', () => {
diff --git a/packages/v4/migration/ClickOutside/ClickOutside.spec.ts b/packages/v4/migration/ClickOutside/ClickOutside.spec.ts
index 717867f6..8bb95eef 100644
--- a/packages/v4/migration/ClickOutside/ClickOutside.spec.ts
+++ b/packages/v4/migration/ClickOutside/ClickOutside.spec.ts
@@ -79,7 +79,7 @@ describe('ClickOutside', () => {
outside.click();
const dropdown = getInstance(
- root.querySelector('[data-component="Dropdown"]')!,
+ root.querySelector('[data-component="Dropdown"]'),
'Dropdown',
)!;
expect(dropdown.closed).toHaveLength(1);
diff --git a/packages/v4/migration/Data/DataBind.spec.ts b/packages/v4/migration/Data/DataBind.spec.ts
index 7b71708b..eb788bf9 100644
--- a/packages/v4/migration/Data/DataBind.spec.ts
+++ b/packages/v4/migration/Data/DataBind.spec.ts
@@ -67,7 +67,7 @@ function el(root: HTMLElement, selector: st
}
function at(root: HTMLElement, selector: string, name: string): T {
- return getInstance(root.querySelector(selector)!, name)! as T;
+ return getInstance(root.querySelector(selector), name)! as T;
}
describe('DataBind — the element half', () => {
diff --git a/packages/v4/migration/Data/DataDerived.spec.ts b/packages/v4/migration/Data/DataDerived.spec.ts
index 3f95defc..8940d2ed 100644
--- a/packages/v4/migration/Data/DataDerived.spec.ts
+++ b/packages/v4/migration/Data/DataDerived.spec.ts
@@ -30,7 +30,7 @@ function el(root: HTMLElement, selector: st
}
function at(root: HTMLElement, selector: string, name: string): T {
- return getInstance(root.querySelector(selector)!, name)! as T;
+ return getInstance(root.querySelector(selector), name)! as T;
}
describe('DataModel', () => {
diff --git a/packages/v4/migration/Data/DataScope.spec.ts b/packages/v4/migration/Data/DataScope.spec.ts
index a4e3ee1c..304d0ea5 100644
--- a/packages/v4/migration/Data/DataScope.spec.ts
+++ b/packages/v4/migration/Data/DataScope.spec.ts
@@ -27,7 +27,7 @@ function uniqueGroup(name: string): string {
}
function at(root: HTMLElement, selector: string, name: string): T {
- return getInstance(root.querySelector(selector)!, name)! as T;
+ return getInstance(root.querySelector(selector), name)! as T;
}
function el(root: HTMLElement, selector: string): T {
diff --git a/packages/v4/migration/Figure/FigureShopify.spec.ts b/packages/v4/migration/Figure/FigureShopify.spec.ts
index 0d9766c0..9233ce42 100644
--- a/packages/v4/migration/Figure/FigureShopify.spec.ts
+++ b/packages/v4/migration/Figure/FigureShopify.spec.ts
@@ -19,7 +19,7 @@ async function render(attributes = ''): Promise {
`;
document.body.append(root);
await settle();
- return getInstance(root.firstElementChild!, 'FigureShopify')!;
+ return getInstance(root.firstElementChild, 'FigureShopify')!;
}
describe('FigureShopify', () => {
diff --git a/packages/v4/migration/Figure/FigureTwicpics.spec.ts b/packages/v4/migration/Figure/FigureTwicpics.spec.ts
index c365a33b..ccdc9eca 100644
--- a/packages/v4/migration/Figure/FigureTwicpics.spec.ts
+++ b/packages/v4/migration/Figure/FigureTwicpics.spec.ts
@@ -20,7 +20,7 @@ async function render(
`;
document.body.append(root);
await settle();
- return getInstance(root.firstElementChild!, 'FigureTwicpics')!;
+ return getInstance(root.firstElementChild, 'FigureTwicpics')!;
}
describe('FigureTwicpics', () => {
diff --git a/packages/v4/migration/FigureVideo/FigureVideoTwicpics.spec.ts b/packages/v4/migration/FigureVideo/FigureVideoTwicpics.spec.ts
index 060300f4..b564ee1e 100644
--- a/packages/v4/migration/FigureVideo/FigureVideoTwicpics.spec.ts
+++ b/packages/v4/migration/FigureVideo/FigureVideoTwicpics.spec.ts
@@ -20,7 +20,7 @@ async function render(attributes = ''): Promise {
`;
document.body.append(root);
await settle();
- return getInstance(root.firstElementChild!, 'FigureVideoTwicpics')!;
+ return getInstance(root.firstElementChild, 'FigureVideoTwicpics')!;
}
describe('FigureVideoTwicpics — the loadSources override', () => {
diff --git a/packages/v4/migration/Menu/Menu.spec.ts b/packages/v4/migration/Menu/Menu.spec.ts
index 521e5424..c060fba1 100644
--- a/packages/v4/migration/Menu/Menu.spec.ts
+++ b/packages/v4/migration/Menu/Menu.spec.ts
@@ -28,7 +28,7 @@ async function render(mode?: string): Promise<{ root: HTMLElement; menu: Menu }>
root.innerHTML = menuMarkup(mode);
document.body.append(root);
await settle();
- return { root, menu: getInstance
`,
);
await settle();
- const label = getInstance