|
| 1 | +import { describe, it, expect } from 'vitest'; |
| 2 | +import { render, screen } from '@testing-library/svelte'; |
| 3 | +import userEvent from '@testing-library/user-event'; |
| 4 | +import BitHex from '@components/blend/BitHex.svelte'; |
| 5 | + |
| 6 | +// BitHex is the critical integration test: it uses bind:integer with both Bits and Digits. |
| 7 | +// Flipping a bit in the Bits row must propagate through the bind: chain and update the Digits row. |
| 8 | +// This test specifically verifies the two-way binding chain is intact after refactoring. |
| 9 | + |
| 10 | +describe('BitHex', () => { |
| 11 | + it('renders checkboxes (bits) and a select (hex digit)', () => { |
| 12 | + render(BitHex, { props: { integer: 0 } }); |
| 13 | + expect(screen.getAllByRole('checkbox').length).toBeGreaterThan(0); |
| 14 | + expect(screen.getAllByRole('combobox').length).toBeGreaterThan(0); |
| 15 | + }); |
| 16 | + |
| 17 | + it('hex select reflects initial integer value', () => { |
| 18 | + render(BitHex, { props: { integer: 0 } }); |
| 19 | + const selects = screen.getAllByRole('combobox') as HTMLSelectElement[]; |
| 20 | + const hexValue = selects.reduce((acc, s, i) => acc + Number(s.value) * Math.pow(16, selects.length - 1 - i), 0); |
| 21 | + expect(hexValue).toBe(0); |
| 22 | + }); |
| 23 | + |
| 24 | + it('shows 3 hex selects for integer 255 with values [0, F, F]', () => { |
| 25 | + // extractBit(255) = [0,1,1,1,1,1,1,1,1] (9 bits including leading zero), |
| 26 | + // padded to 12 bits → lengthOfBits=12 → Math.ceil(12/4)=3 hex digits. |
| 27 | + // The leading-zero nibble gives a first select value of 0. |
| 28 | + render(BitHex, { props: { integer: 255 } }); |
| 29 | + const selects = screen.getAllByRole('combobox') as HTMLSelectElement[]; |
| 30 | + expect(selects).toHaveLength(3); |
| 31 | + expect(Number(selects[0].value)).toBe(0); |
| 32 | + expect(Number(selects[1].value)).toBe(15); |
| 33 | + expect(Number(selects[2].value)).toBe(15); |
| 34 | + }); |
| 35 | + |
| 36 | + it('shows 12 bit cells for integer 255, first 4 unchecked', () => { |
| 37 | + // extractBit(255) = [0,1,...,1] (9 elements) → padded to 12 bits |
| 38 | + // First 4 bits (the leading-zero nibble) are 0; last 8 bits are 1. |
| 39 | + render(BitHex, { props: { integer: 255 } }); |
| 40 | + const cbs = screen.getAllByRole('checkbox') as HTMLInputElement[]; |
| 41 | + expect(cbs).toHaveLength(12); |
| 42 | + expect(cbs.slice(0, 4).every((cb) => !cb.checked)).toBe(true); |
| 43 | + expect(cbs.slice(4).every((cb) => cb.checked)).toBe(true); |
| 44 | + }); |
| 45 | + |
| 46 | + it('flipping LSB bit from 0 → 1 updates hex digit (bind: chain)', async () => { |
| 47 | + // This is the core integration test: |
| 48 | + // Bit flip → Bits.integer updates → bind:integer propagates to BitHex → Digits re-renders |
| 49 | + const user = userEvent.setup(); |
| 50 | + render(BitHex, { props: { integer: 0 } }); |
| 51 | + |
| 52 | + const cbs = screen.getAllByRole('checkbox'); |
| 53 | + const selectsBefore = screen.getAllByRole('combobox') as HTMLSelectElement[]; |
| 54 | + // All hex selects start at 0 |
| 55 | + expect(selectsBefore.every((s) => Number(s.value) === 0)).toBe(true); |
| 56 | + |
| 57 | + // Flip the LSB |
| 58 | + await user.click(cbs[cbs.length - 1]); |
| 59 | + |
| 60 | + const selectsAfter = screen.getAllByRole('combobox') as HTMLSelectElement[]; |
| 61 | + // The last hex select (LSB nibble) should now be 1 |
| 62 | + expect(Number(selectsAfter[selectsAfter.length - 1].value)).toBe(1); |
| 63 | + }); |
| 64 | + |
| 65 | + it('flipping LSB bit from 1 → 0 updates hex digit back to 0', async () => { |
| 66 | + const user = userEvent.setup(); |
| 67 | + render(BitHex, { props: { integer: 1 } }); |
| 68 | + |
| 69 | + const cbs = screen.getAllByRole('checkbox'); |
| 70 | + const lsb = cbs[cbs.length - 1]; |
| 71 | + expect(lsb).toBeChecked(); |
| 72 | + |
| 73 | + await user.click(lsb); |
| 74 | + |
| 75 | + const selects = screen.getAllByRole('combobox') as HTMLSelectElement[]; |
| 76 | + expect(selects.every((s) => Number(s.value) === 0)).toBe(true); |
| 77 | + }); |
| 78 | + |
| 79 | + it('flipping the MSB of integer 0 checks that bit and makes the hex non-zero', async () => { |
| 80 | + // Integer 0 → 4 bits, MSB at index 0 represents 2^3=8. |
| 81 | + // After flip: integer=8, hex select shows 8. |
| 82 | + const user = userEvent.setup(); |
| 83 | + render(BitHex, { props: { integer: 0 } }); |
| 84 | + |
| 85 | + const cbs = screen.getAllByRole('checkbox'); |
| 86 | + expect(cbs).toHaveLength(4); |
| 87 | + const msb = cbs[0]; |
| 88 | + expect(msb).not.toBeChecked(); |
| 89 | + |
| 90 | + await user.click(msb); |
| 91 | + |
| 92 | + // MSB is now checked |
| 93 | + expect(msb).toBeChecked(); |
| 94 | + |
| 95 | + // integer became 8 = 0x8; hex selects now show non-zero |
| 96 | + const selectsAfter = screen.getAllByRole('combobox') as HTMLSelectElement[]; |
| 97 | + const hexValue = selectsAfter.reduce( |
| 98 | + (acc, s, i) => acc + Number(s.value) * Math.pow(16, selectsAfter.length - 1 - i), |
| 99 | + 0 |
| 100 | + ); |
| 101 | + expect(hexValue).toBe(8); |
| 102 | + }); |
| 103 | +}); |
0 commit comments