|
|
@@ -6,7 +6,7 @@
|
|
|
*/
|
|
|
|
|
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
|
-import { screen, fireEvent, cleanup, waitFor } from '@testing-library/react';
|
|
|
+import { screen, fireEvent, cleanup, waitFor, within } from '@testing-library/react';
|
|
|
import { http, HttpResponse } from 'msw';
|
|
|
import { render } from '../utils';
|
|
|
import { server } from '../mocks/server';
|
|
|
@@ -27,6 +27,33 @@ const defaultAvailable = [
|
|
|
|
|
|
const mockOnChange = vi.fn();
|
|
|
|
|
|
+/**
|
|
|
+ * Open an override picker and hand back its rows.
|
|
|
+ *
|
|
|
+ * These assertions used to read `select.querySelectorAll('option')`. The
|
|
|
+ * control gained a swatch per choice (#3159), which an `<option>` cannot
|
|
|
+ * render, so it is a listbox now: its rows exist only while it is open, and
|
|
|
+ * each carries its identity on `data-value` the way `<option value>` did.
|
|
|
+ */
|
|
|
+function openPicker(index = 0): HTMLElement[] {
|
|
|
+ fireEvent.click(screen.getAllByRole('combobox')[index]);
|
|
|
+ return within(screen.getByRole('listbox')).getAllByRole('option');
|
|
|
+}
|
|
|
+
|
|
|
+/** The identities the picker offers, in order — the old `option.value` list. */
|
|
|
+function optionValues(index = 0): (string | null)[] {
|
|
|
+ return openPicker(index).map((o) => o.getAttribute('data-value'));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * What the closed control reads as. With nothing overridden this is the
|
|
|
+ * "original filament" row, which is where the #1718 label resolution lands —
|
|
|
+ * previously read off `option[value=""]`.
|
|
|
+ */
|
|
|
+function triggerText(index = 0): string {
|
|
|
+ return screen.getAllByRole('combobox')[index].textContent ?? '';
|
|
|
+}
|
|
|
+
|
|
|
afterEach(() => {
|
|
|
cleanup();
|
|
|
vi.clearAllMocks();
|
|
|
@@ -122,15 +149,10 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const options = select.querySelectorAll('option');
|
|
|
-
|
|
|
- // 1 default "Original" option + 2 PLA options (not PETG)
|
|
|
- expect(options).toHaveLength(3);
|
|
|
-
|
|
|
- // Verify no PETG option values exist
|
|
|
- const optionValues = Array.from(options).map((o) => o.getAttribute('value'));
|
|
|
- expect(optionValues).not.toContain('PETG|#0000FF');
|
|
|
+ // 1 default "Original" row + 2 PLA rows (not PETG)
|
|
|
+ const values = optionValues();
|
|
|
+ expect(values).toHaveLength(3);
|
|
|
+ expect(values).not.toContain('PETG|#0000FF');
|
|
|
});
|
|
|
|
|
|
it('shows all same-type options regardless of color', () => {
|
|
|
@@ -149,11 +171,8 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const options = select.querySelectorAll('option');
|
|
|
-
|
|
|
- // 1 default "Original" option + 3 PLA color options
|
|
|
- expect(options).toHaveLength(4);
|
|
|
+ // 1 default "Original" row + 3 PLA colour rows
|
|
|
+ expect(optionValues()).toHaveLength(4);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
@@ -173,9 +192,7 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const options = Array.from(select.querySelectorAll('option'));
|
|
|
- const optionTexts = options.map((o) => o.textContent);
|
|
|
+ const optionTexts = openPicker().map((o) => o.textContent);
|
|
|
|
|
|
// Should show "PLA Basic" and "PLA Matte", not just "PLA"
|
|
|
expect(optionTexts.some((t) => t?.includes('PLA Basic'))).toBe(true);
|
|
|
@@ -196,11 +213,9 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const options = Array.from(select.querySelectorAll('option'));
|
|
|
- // Non-default option should show "PLA" as the type fallback
|
|
|
- const nonDefaultOptions = options.filter((o) => o.getAttribute('value') !== '');
|
|
|
- expect(nonDefaultOptions[0].textContent).toContain('PLA');
|
|
|
+ // Non-default row should show "PLA" as the type fallback
|
|
|
+ const nonDefault = openPicker().filter((o) => o.getAttribute('data-value') !== '');
|
|
|
+ expect(nonDefault[0].textContent).toContain('PLA');
|
|
|
});
|
|
|
});
|
|
|
|
|
|
@@ -226,15 +241,11 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const options = select.querySelectorAll('option');
|
|
|
-
|
|
|
// 1 default + 1 PLA with extruder_id=0 (extruder_id=1 is filtered out)
|
|
|
- expect(options).toHaveLength(2);
|
|
|
-
|
|
|
- const optionValues = Array.from(options).map((o) => o.getAttribute('value'));
|
|
|
- expect(optionValues).toContain('PLA|#FF0000');
|
|
|
- expect(optionValues).not.toContain('PLA|#00FF00');
|
|
|
+ const values = optionValues();
|
|
|
+ expect(values).toHaveLength(2);
|
|
|
+ expect(values).toContain('PLA|#FF0000');
|
|
|
+ expect(values).not.toContain('PLA|#00FF00');
|
|
|
});
|
|
|
|
|
|
it('shows all filaments when nozzle_id is undefined', () => {
|
|
|
@@ -258,11 +269,8 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const options = select.querySelectorAll('option');
|
|
|
-
|
|
|
- // 1 default + 2 PLA options (no nozzle filtering)
|
|
|
- expect(options).toHaveLength(3);
|
|
|
+ // 1 default + 2 PLA rows (no nozzle filtering)
|
|
|
+ expect(optionValues()).toHaveLength(3);
|
|
|
});
|
|
|
|
|
|
it('includes filaments with null extruder_id', () => {
|
|
|
@@ -287,16 +295,12 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const options = select.querySelectorAll('option');
|
|
|
-
|
|
|
// 1 default + extruder_id=0 + extruder_id=null (extruder_id=1 filtered out)
|
|
|
- expect(options).toHaveLength(3);
|
|
|
-
|
|
|
- const optionValues = Array.from(options).map((o) => o.getAttribute('value'));
|
|
|
- expect(optionValues).toContain('PLA|#FF0000');
|
|
|
- expect(optionValues).toContain('PLA|#00FF00');
|
|
|
- expect(optionValues).not.toContain('PLA|#FFFFFF');
|
|
|
+ const values = optionValues();
|
|
|
+ expect(values).toHaveLength(3);
|
|
|
+ expect(values).toContain('PLA|#FF0000');
|
|
|
+ expect(values).toContain('PLA|#00FF00');
|
|
|
+ expect(values).not.toContain('PLA|#FFFFFF');
|
|
|
});
|
|
|
});
|
|
|
|
|
|
@@ -311,8 +315,8 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- fireEvent.change(select, { target: { value: 'PLA|#00FF00' } });
|
|
|
+ const row = openPicker().find((o) => o.getAttribute('data-value') === 'PLA|#00FF00');
|
|
|
+ fireEvent.click(row as HTMLElement);
|
|
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
|
1: { type: 'PLA', color: '#00FF00' },
|
|
|
@@ -333,13 +337,60 @@ describe('FilamentOverride', () => {
|
|
|
/>
|
|
|
);
|
|
|
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- fireEvent.change(select, { target: { value: '' } });
|
|
|
+ const row = openPicker().find((o) => o.getAttribute('data-value') === '');
|
|
|
+ fireEvent.click(row as HTMLElement);
|
|
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({});
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+ describe('colour swatches (#3159)', () => {
|
|
|
+ it('draws the colour of every filament it offers, and of the original', () => {
|
|
|
+ render(
|
|
|
+ <FilamentOverride
|
|
|
+ filamentReqs={defaultFilamentReqs}
|
|
|
+ availableFilaments={defaultAvailable}
|
|
|
+ overrides={{}}
|
|
|
+ onChange={mockOnChange}
|
|
|
+ />
|
|
|
+ );
|
|
|
+
|
|
|
+ const rows = openPicker();
|
|
|
+ // The original-filament row plus the two same-type candidates: model
|
|
|
+ // mode pools across printers, so there is no slot binding behind an
|
|
|
+ // entry and each swatch is the plain tray colour.
|
|
|
+ expect(rows).toHaveLength(3);
|
|
|
+ for (const row of rows) {
|
|
|
+ expect(within(row).getByTestId('filament-swatch')).toBeInTheDocument();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('prints each candidate hex and marks the one the slice asked for', () => {
|
|
|
+ render(
|
|
|
+ <FilamentOverride
|
|
|
+ filamentReqs={defaultFilamentReqs}
|
|
|
+ availableFilaments={defaultAvailable}
|
|
|
+ overrides={{}}
|
|
|
+ onChange={mockOnChange}
|
|
|
+ />
|
|
|
+ );
|
|
|
+
|
|
|
+ const rows = openPicker();
|
|
|
+ const listbox = screen.getByRole('listbox');
|
|
|
+ expect(within(listbox).getByText('#00FF00')).toBeInTheDocument();
|
|
|
+ // #FF0000 twice: the original-filament row states what the slice asked
|
|
|
+ // for, and one candidate carries it.
|
|
|
+ expect(within(listbox).getAllByText('#FF0000')).toHaveLength(2);
|
|
|
+
|
|
|
+ // The marker belongs on the candidate, not on the original — it answers
|
|
|
+ // "which of these is the colour the slice asked for", which is the
|
|
|
+ // judgement the colour names cannot be trusted to make.
|
|
|
+ const marked = rows.filter((r) => within(r).queryByTitle('Exact colour match'));
|
|
|
+ expect(marked).toHaveLength(1);
|
|
|
+ expect(marked[0].getAttribute('data-value')).toBe('PLA|#FF0000');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
describe('original-label SKU resolution (#1718)', () => {
|
|
|
it('uses the builtin filament name when tray_info_idx maps to a known SKU', async () => {
|
|
|
// Stamped by Bambu Studio when slicing with PLA Matte Charcoal: 3MF
|
|
|
@@ -367,15 +418,11 @@ describe('FilamentOverride', () => {
|
|
|
/>,
|
|
|
);
|
|
|
|
|
|
- // Wait for the queries to resolve and the resolved label to land in
|
|
|
- // the dropdown's "original" placeholder option. The tooltip on the
|
|
|
- // color swatch carries the same text, so we scope to the option to
|
|
|
- // avoid the multi-match.
|
|
|
- await waitFor(() => {
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const placeholder = select.querySelector('option[value=""]');
|
|
|
- expect(placeholder?.textContent).toMatch(/Bambu PLA Matte/);
|
|
|
- });
|
|
|
+ // Wait for the queries to resolve and the resolved label to land on the
|
|
|
+ // control, which reads as the "original filament" row while nothing is
|
|
|
+ // overridden. Reading the closed trigger rather than the open list keeps
|
|
|
+ // this clear of the swatch tooltip, which carries the same text.
|
|
|
+ await waitFor(() => expect(triggerText()).toMatch(/Bambu PLA Matte/));
|
|
|
});
|
|
|
|
|
|
it('prefers the cloud user-preset name over the builtin entry for the same id', async () => {
|
|
|
@@ -405,11 +452,7 @@ describe('FilamentOverride', () => {
|
|
|
/>,
|
|
|
);
|
|
|
|
|
|
- await waitFor(() => {
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const placeholder = select.querySelector('option[value=""]');
|
|
|
- expect(placeholder?.textContent).toMatch(/My House PLA/);
|
|
|
- });
|
|
|
+ await waitFor(() => expect(triggerText()).toMatch(/My House PLA/));
|
|
|
// The builtin fallback must NOT bleed through anywhere — neither the
|
|
|
// placeholder option nor the tooltip.
|
|
|
expect(screen.queryByText(/Bambu PLA Basic/)).not.toBeInTheDocument();
|
|
|
@@ -452,11 +495,7 @@ describe('FilamentOverride', () => {
|
|
|
/>,
|
|
|
);
|
|
|
|
|
|
- await waitFor(() => {
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const placeholder = select.querySelector('option[value=""]');
|
|
|
- expect(placeholder?.textContent).toMatch(/Bambu PLA Matte \(Charcoal\)/);
|
|
|
- });
|
|
|
+ await waitFor(() => expect(triggerText()).toMatch(/Bambu PLA Matte \(Charcoal\)/));
|
|
|
});
|
|
|
|
|
|
it('disambiguates per slot when two slots share a hex but differ in material', async () => {
|
|
|
@@ -497,10 +536,9 @@ describe('FilamentOverride', () => {
|
|
|
);
|
|
|
|
|
|
await waitFor(() => {
|
|
|
- const selects = screen.getAllByRole('combobox');
|
|
|
- expect(selects).toHaveLength(2);
|
|
|
- expect(selects[0].querySelector('option[value=""]')?.textContent).toMatch(/Bambu PLA Matte \(Charcoal\)/);
|
|
|
- expect(selects[1].querySelector('option[value=""]')?.textContent).toMatch(/Bambu PLA Basic \(Black\)/);
|
|
|
+ expect(screen.getAllByRole('combobox')).toHaveLength(2);
|
|
|
+ expect(triggerText(0)).toMatch(/Bambu PLA Matte \(Charcoal\)/);
|
|
|
+ expect(triggerText(1)).toMatch(/Bambu PLA Basic \(Black\)/);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
@@ -536,10 +574,8 @@ describe('FilamentOverride', () => {
|
|
|
// Wait for the builtin lookup to land so we know the row mounted; the
|
|
|
// colour fallback to getColorName for #FF0000 produces "Red"-shaped text.
|
|
|
await waitFor(() => {
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const placeholder = select.querySelector('option[value=""]');
|
|
|
- expect(placeholder?.textContent).toMatch(/Bambu PLA Matte/);
|
|
|
- expect(placeholder?.textContent).not.toMatch(/null/);
|
|
|
+ expect(triggerText()).toMatch(/Bambu PLA Matte/);
|
|
|
+ expect(triggerText()).not.toMatch(/null/);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
@@ -571,9 +607,7 @@ describe('FilamentOverride', () => {
|
|
|
await waitFor(() => {
|
|
|
expect(screen.getByText('(25g)')).toBeInTheDocument();
|
|
|
});
|
|
|
- const select = screen.getByRole('combobox');
|
|
|
- const placeholder = select.querySelector('option[value=""]');
|
|
|
- expect(placeholder?.textContent).toMatch(/PLA \(/);
|
|
|
+ expect(triggerText()).toMatch(/PLA \(/);
|
|
|
});
|
|
|
});
|
|
|
});
|