test: harden controller remap selectors

This commit is contained in:
2026-03-14 21:32:52 -07:00
parent 81e80b8cf5
commit e6d9c59aad
4 changed files with 55 additions and 30 deletions

View File

@@ -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<Pick<TestGamepad, 'index' | 'axes' | 'buttons'>> = {},
@@ -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 ?? {}),
}),
},

View File

@@ -27,6 +27,7 @@ function createClassList(initialTokens: string[] = []) {
}
function createFakeElement() {
const attributes = new Map<string, string>();
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;
},
};
}

View File

@@ -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', () => {

View File

@@ -28,6 +28,7 @@ function createClassList(initialTokens: string[] = []) {
}
function createFakeElement() {
const attributes = new Map<string, string>();
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<typeof createFakeElement>, labelText: string) {
return container.children.find((child) => child.children?.[0]?.textContent === labelText) ?? null;
function getByTestId(container: ReturnType<typeof createFakeElement>, 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];