Skip to content

Commit d031497

Browse files
committed
Keep selected reference presets visible in quick catalog filters
1 parent c249311 commit d031497

3 files changed

Lines changed: 77 additions & 21 deletions

File tree

src/components/QuickPicks.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export default function QuickPicks({ part, parameters, presetId, onSelect, onBro
2929
const pageCount = Math.max(1, Math.ceil(matches.length / 4));
3030
const visiblePage = Math.min(page, pageCount - 1);
3131
useEffect(() => {
32-
setFilters((current) => reconcileQuickPickFilters(part, parameters, current));
33-
}, [part, parameters]);
32+
setFilters((current) => reconcileQuickPickFilters(part, parameters, current, presetId));
33+
}, [part, parameters, presetId]);
3434
useEffect(() => {
3535
const selectedIndex = matches.findIndex((preset) => preset.id === presetId);
3636
if (selectedIndex >= 0) setPage(Math.floor(selectedIndex / 4));

src/core/quick-picks.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,24 @@ export function reconcileQuickPickFilters(
105105
part: PartDefinition,
106106
parameters: Parameters,
107107
filters: QuickPickFilters,
108+
presetId: string,
108109
): QuickPickFilters {
109110
const fields = quickPickFields(part);
110-
const entries = Object.entries(filters).filter(([key]) =>
111-
fields.some((field) => field.key === key),
111+
const selected = part.presets.find((preset) => preset.id === presetId && preset.catalog);
112+
const entries = Object.entries(filters).filter(
113+
([key, value]) =>
114+
fields.some((field) => field.key === key) &&
115+
(!value || !selected || selected.catalog!.verifiedParameters.includes(key)),
112116
);
113117
if (entries.every(([key, value]) => !value || String(parameters[key]) === value))
114118
return entries.length === Object.keys(filters).length ? filters : Object.fromEntries(entries);
115-
const matchingPreset = part.presets.find(
116-
(preset) =>
117-
preset.catalog &&
118-
fields.every((field) => preset.parameters[field.key] === parameters[field.key]),
119-
);
119+
const matchingPreset =
120+
selected ??
121+
part.presets.find(
122+
(preset) =>
123+
preset.catalog &&
124+
fields.every((field) => preset.parameters[field.key] === parameters[field.key]),
125+
);
120126
const next = Object.fromEntries(
121127
fields
122128
.filter((field) => matchingPreset?.catalog?.verifiedParameters.includes(field.key))

tests/quick-picks.test.ts

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import test from 'node:test';
22
import assert from 'node:assert/strict';
33
import { parts } from '../src/parts';
44
import { validateParameters } from '../src/core/validation';
5+
import type { QuickPickFilters } from '../src/core/quick-picks';
56
import {
67
closestQuickPick,
78
initialQuickPickFilters,
@@ -49,6 +50,7 @@ test('catalog selectors come from each module and do not depend on its name or c
4950
independent,
5051
{ diameter: 3, drive: 'hex-socket' },
5152
{ diameter: '3', length: '8' },
53+
'',
5254
),
5355
{ diameter: '3' },
5456
);
@@ -126,17 +128,25 @@ test('nuts and ball screws expose their natural standard size choices', () => {
126128
test('external selections reconcile stale filters while progressive choices remain unset', () => {
127129
const chosen = quickPickMatches(bolt.presets, { diameter: '8', length: '30', head: 'hex' })[0];
128130
assert.ok(chosen);
129-
const updated = reconcileQuickPickFilters(bolt, chosen.parameters, {
130-
diameter: '2',
131-
length: '8',
132-
});
131+
const updated = reconcileQuickPickFilters(
132+
bolt,
133+
chosen.parameters,
134+
{
135+
diameter: '2',
136+
length: '8',
137+
},
138+
chosen.id,
139+
);
133140
assert.equal(updated.diameter, '8');
134141
assert.equal(updated.length, '30');
135142
assert.ok(quickPickMatches(bolt.presets, updated).includes(chosen));
136-
assert.deepEqual(reconcileQuickPickFilters(bolt, chosen.parameters, { diameter: '8' }), {
137-
diameter: '8',
138-
});
139-
assert.deepEqual(reconcileQuickPickFilters(bolt, chosen.parameters, {}), {});
143+
assert.deepEqual(
144+
reconcileQuickPickFilters(bolt, chosen.parameters, { diameter: '8' }, chosen.id),
145+
{
146+
diameter: '8',
147+
},
148+
);
149+
assert.deepEqual(reconcileQuickPickFilters(bolt, chosen.parameters, {}, chosen.id), {});
140150
assert.equal(hasCatalogQuickSize(bolt, chosen.parameters), true);
141151
assert.equal(hasCatalogQuickSize(bolt, { ...chosen.parameters, diameter: 6.3 }), false);
142152
assert.equal(hasCatalogQuickSize(bolt, { ...chosen.parameters, threadMode: 'envelope' }), true);
@@ -163,11 +173,51 @@ test('sourced ball screws keep catalog mode when assembly length is a prototype
163173
)!;
164174
assert.ok(preset);
165175
assert.equal(hasCatalogQuickSize(screw, preset.parameters), true);
166-
const filters = reconcileQuickPickFilters(screw, preset.parameters, {
167-
family: 'SFK',
168-
length: '100',
169-
});
176+
const filters = reconcileQuickPickFilters(
177+
screw,
178+
preset.parameters,
179+
{
180+
family: 'SFK',
181+
length: '100',
182+
},
183+
preset.id,
184+
);
170185
assert.equal(filters.family, 'SFU');
171186
assert.equal(filters.length, undefined);
172187
assert.ok(quickPickMatches(screw.presets, filters).includes(preset));
173188
});
189+
190+
test('the selected DIN 915 reference retains its own source scope beside equal-size stock screws', () => {
191+
const screw = parts.find((part) => part.id === 'set-screw')!;
192+
const chosen = screw.presets.find((preset) => preset.id === 'reference-din915-m6')!;
193+
assert.ok(
194+
screw.presets.some(
195+
(preset) =>
196+
preset.id !== chosen.id &&
197+
preset.parameters.diameter === 6 &&
198+
preset.parameters.length === 16 &&
199+
preset.parameters.tip === 'dog',
200+
),
201+
);
202+
const preceding: QuickPickFilters[] = [
203+
{ diameter: '2.5', length: '8' },
204+
{ diameter: '6', length: '16', tip: 'dog', drive: 'hex' },
205+
];
206+
for (const before of preceding) {
207+
const filters = reconcileQuickPickFilters(screw, chosen.parameters, before, chosen.id);
208+
assert.equal(
209+
filters.length,
210+
undefined,
211+
'Prototype length must not become a stock-length constraint',
212+
);
213+
assert.ok(
214+
quickPickMatches(screw.presets, filters).includes(chosen),
215+
'Selected reference remains visible',
216+
);
217+
}
218+
const progressive = { diameter: '6' };
219+
assert.deepEqual(
220+
reconcileQuickPickFilters(screw, chosen.parameters, progressive, chosen.id),
221+
progressive,
222+
);
223+
});

0 commit comments

Comments
 (0)