|
1 | 1 | import { describe, expect, it, vi } from 'vitest'; |
2 | 2 | import { useOptionsData } from '@/composables/useOptionsData.js'; |
| 3 | +import { defaultOptions } from '@/definitions.js'; |
3 | 4 |
|
4 | 5 | // the composable's internal deep watch on `options` fires (asynchronously, via Vue's |
5 | 6 | // reactivity flush) on every mutation made below and calls messenger.storage.local.set() |
6 | 7 | // plus setTheme(..., document.body, ...) - stub both globally for the whole file so the |
7 | 8 | // watcher's side effects don't throw, regardless of exactly when its microtask fires |
8 | 9 | // relative to a given test's completion |
9 | 10 | const fakeElement = () => ({ classList: { add: () => {}, remove: () => {}, contains: () => false } }); |
10 | | -vi.stubGlobal('messenger', { storage: { local: { set: vi.fn() } } }); |
| 11 | +vi.stubGlobal('messenger', { |
| 12 | + storage: { local: { set: vi.fn() } }, |
| 13 | + // only needed by resetOptions -> getAccounts |
| 14 | + runtime: { getBackgroundPage: async () => ({ messenger: { accounts: { list: async () => [] } } }) }, |
| 15 | +}); |
11 | 16 | vi.stubGlobal('document', { body: fakeElement(), documentElement: fakeElement() }); |
12 | 17 | vi.stubGlobal('window', { matchMedia: () => ({ matches: false }) }); |
13 | 18 |
|
| 19 | +describe('options initial state (defaultOptions aliasing regression)', () => { |
| 20 | + it('is not the same object reference as the shared defaultOptions singleton', () => { |
| 21 | + const { options } = useOptionsData(); |
| 22 | + expect(options.value).not.toBe(defaultOptions); |
| 23 | + }); |
| 24 | + |
| 25 | + it('mutating options does not mutate the shared defaultOptions singleton', () => { |
| 26 | + const { options } = useOptionsData(); |
| 27 | + options.value.theme = 'dark'; |
| 28 | + options.value.accountColors.abc = '#123456'; |
| 29 | + expect(defaultOptions.theme).toBe('system'); |
| 30 | + expect(defaultOptions.accountColors).toEqual({}); |
| 31 | + }); |
| 32 | + |
| 33 | + it('resetOptions() re-clones defaultOptions rather than re-aliasing it', async () => { |
| 34 | + const { options, resetOptions } = useOptionsData(); |
| 35 | + options.value.theme = 'dark'; |
| 36 | + await resetOptions(); |
| 37 | + expect(options.value).not.toBe(defaultOptions); |
| 38 | + options.value.debug = true; |
| 39 | + expect(defaultOptions.debug).toBe(false); |
| 40 | + }); |
| 41 | +}); |
| 42 | + |
| 43 | +describe('addAddress / removeAddress (no redundant partial storage write)', () => { |
| 44 | + it('addAddress appends the given address and clears the input', () => { |
| 45 | + const { input, options, addAddress, addressList } = useOptionsData(); |
| 46 | + options.value.addresses = ''; |
| 47 | + input.value.address = 'a@example.com'; |
| 48 | + addAddress(); |
| 49 | + expect(options.value.addresses).toBe('a@example.com'); |
| 50 | + expect(addressList.value).toEqual(['a@example.com']); |
| 51 | + expect(input.value.address).toBe(''); |
| 52 | + }); |
| 53 | + |
| 54 | + it('addAddress appends to existing addresses with a comma separator', () => { |
| 55 | + const { input, options, addAddress } = useOptionsData(); |
| 56 | + options.value.addresses = 'a@example.com'; |
| 57 | + input.value.address = 'b@example.com'; |
| 58 | + addAddress(); |
| 59 | + expect(options.value.addresses).toBe('a@example.com,b@example.com'); |
| 60 | + }); |
| 61 | + |
| 62 | + it('removeAddress removes only the given address and normalizes separators', () => { |
| 63 | + const { options, removeAddress, addressList } = useOptionsData(); |
| 64 | + options.value.addresses = 'a@example.com,b@example.com,c@example.com'; |
| 65 | + removeAddress('b@example.com'); |
| 66 | + expect(addressList.value).toEqual(['a@example.com', 'c@example.com']); |
| 67 | + }); |
| 68 | +}); |
| 69 | + |
14 | 70 | describe('addressList', () => { |
15 | 71 | it('is empty when addresses is unset', () => { |
16 | 72 | const { options, addressList } = useOptionsData(); |
|
0 commit comments