diff --git a/src/renderer/handlers/gamepad-controller.test.ts b/src/renderer/handlers/gamepad-controller.test.ts index 120b38ed..c6f3641a 100644 --- a/src/renderer/handlers/gamepad-controller.test.ts +++ b/src/renderer/handlers/gamepad-controller.test.ts @@ -13,6 +13,20 @@ type TestGamepad = { buttons: Array<{ value: number; pressed?: boolean; touched?: boolean }>; }; +const DEFAULT_BUTTON_INDICES = { + select: 6, + buttonSouth: 0, + buttonEast: 1, + buttonWest: 2, + buttonNorth: 3, + leftShoulder: 4, + rightShoulder: 5, + leftStickPress: 9, + rightStickPress: 10, + leftTrigger: 6, + rightTrigger: 7, +} satisfies ResolvedControllerConfig['buttonIndices']; + function createGamepad( id: string, options: Partial> = {}, @@ -57,17 +71,7 @@ function createControllerConfig( repeatDelayMs: 320, repeatIntervalMs: 120, buttonIndices: { - select: 6, - buttonSouth: 0, - buttonEast: 1, - buttonWest: 2, - buttonNorth: 3, - leftShoulder: 4, - rightShoulder: 5, - leftStickPress: 9, - rightStickPress: 10, - leftTrigger: 6, - rightTrigger: 7, + ...DEFAULT_BUTTON_INDICES, ...(buttonIndexOverrides ?? {}), }, bindings: { @@ -85,17 +89,7 @@ function createControllerConfig( rightStickHorizontal: { kind: 'axis', axisIndex: 3, dpadFallback: 'none' }, rightStickVertical: { kind: 'axis', axisIndex: 4, dpadFallback: 'none' }, ...normalizeBindingOverrides(bindingOverrides ?? {}, { - select: 6, - buttonSouth: 0, - buttonEast: 1, - buttonWest: 2, - buttonNorth: 3, - leftShoulder: 4, - rightShoulder: 5, - leftStickPress: 9, - rightStickPress: 10, - leftTrigger: 6, - rightTrigger: 7, + ...DEFAULT_BUTTON_INDICES, ...(buttonIndexOverrides ?? {}), }), }, diff --git a/src/renderer/modals/controller-config-form.test.ts b/src/renderer/modals/controller-config-form.test.ts index e99d16e7..6d6f2a7f 100644 --- a/src/renderer/modals/controller-config-form.test.ts +++ b/src/renderer/modals/controller-config-form.test.ts @@ -27,6 +27,7 @@ function createClassList(initialTokens: string[] = []) { } function createFakeElement() { + const attributes = new Map(); return { className: '', textContent: '', @@ -48,6 +49,12 @@ function createFakeElement() { listener(); } }, + setAttribute(name: string, value: string) { + attributes.set(name, value); + }, + getAttribute(name: string) { + return attributes.get(name) ?? null; + }, }; } diff --git a/src/renderer/modals/controller-config-form.ts b/src/renderer/modals/controller-config-form.ts index b2dc7ebe..dc67e455 100644 --- a/src/renderer/modals/controller-config-form.ts +++ b/src/renderer/modals/controller-config-form.ts @@ -161,6 +161,7 @@ export function createControllerConfigForm(options: { const row = document.createElement('div'); row.className = 'controller-config-row'; + row.setAttribute('data-testid', `controller-row-${definition.id}`); row.classList.toggle('learning', options.getLearningActionId() === definition.id); const label = document.createElement('div'); @@ -177,6 +178,7 @@ export function createControllerConfigForm(options: { const learnButton = document.createElement('button'); learnButton.type = 'button'; learnButton.className = 'kiku-confirm-button'; + learnButton.setAttribute('data-testid', 'learn-button'); learnButton.textContent = options.getLearningActionId() === definition.id ? 'Learning...' : 'Learn'; learnButton.addEventListener('click', () => { diff --git a/src/renderer/modals/controller-select.test.ts b/src/renderer/modals/controller-select.test.ts index 7f1a6fc8..b00728f0 100644 --- a/src/renderer/modals/controller-select.test.ts +++ b/src/renderer/modals/controller-select.test.ts @@ -28,6 +28,7 @@ function createClassList(initialTokens: string[] = []) { } function createFakeElement() { + const attributes = new Map(); return { className: '', textContent: '', @@ -53,7 +54,27 @@ function createFakeElement() { listener(); } }, - setAttribute: () => {}, + setAttribute(name: string, value: string) { + attributes.set(name, value); + }, + getAttribute(name: string) { + return attributes.get(name) ?? null; + }, + querySelector(selector: string) { + const match = selector.match(/^\[data-testid="(.+)"\]$/); + if (!match) return null; + const testId = match[1]; + for (const child of this.children) { + if (typeof child.getAttribute === 'function' && child.getAttribute('data-testid') === testId) { + return child; + } + if (typeof child.querySelector === 'function') { + const nested = child.querySelector(selector); + if (nested) return nested; + } + } + return null; + }, focus: () => {}, }; } @@ -142,8 +163,10 @@ function buildContext() { return { state, dom }; } -function findActionRow(container: ReturnType, labelText: string) { - return container.children.find((child) => child.children?.[0]?.textContent === labelText) ?? null; +function getByTestId(container: ReturnType, testId: string) { + const element = container.querySelector(`[data-testid="${testId}"]`); + assert.ok(element); + return element; } test('controller select modal saves preferred controller from dropdown selection', async () => { @@ -218,8 +241,8 @@ test('controller select modal learn mode captures fresh button input and persist modal.wireDomEvents(); modal.openControllerSelectModal(); - const firstRow = dom.controllerConfigList.children[1]; - const learnButton = firstRow.children[2].children[0]; + const firstRow = getByTestId(dom.controllerConfigList, 'controller-row-toggleLookup'); + const learnButton = getByTestId(firstRow, 'learn-button'); learnButton.dispatch('click'); state.controllerRawButtons = Array.from({ length: 12 }, () => ({ @@ -278,9 +301,8 @@ test('controller select modal preserves saved axis dpad fallback while relearnin modal.openControllerSelectModal(); - const tokenMoveRow = findActionRow(dom.controllerConfigList, 'Token Move'); - assert.ok(tokenMoveRow); - const learnButton = tokenMoveRow.children[2].children[0]; + const tokenMoveRow = getByTestId(dom.controllerConfigList, 'controller-row-leftStickHorizontal'); + const learnButton = getByTestId(tokenMoveRow, 'learn-button'); learnButton.dispatch('click'); state.controllerRawAxes = [0, 0, 0.85];