printerCardPrefs.test.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * Per-printer printer-card view preferences (#1782).
  3. *
  4. * The store is keyed by printer id so the toggle on one card cannot rearrange
  5. * another, and it has to survive whatever is already sitting in localStorage —
  6. * a value from an older format, or one another tab mangled — without throwing
  7. * out of a render.
  8. *
  9. * The shared test setup stubs localStorage with bare vi.fn()s that store
  10. * nothing, so this file backs them with a real in-memory object; a round-trip
  11. * is the whole point of what's under test here.
  12. */
  13. import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
  14. import {
  15. isExternalSpoolHidden,
  16. setExternalSpoolHidden,
  17. } from '../../utils/printerCardPrefs';
  18. const KEY = 'printerHiddenExternalSpools';
  19. let store: Record<string, string>;
  20. function stored(): unknown {
  21. return JSON.parse(store[KEY]);
  22. }
  23. describe('printerCardPrefs — external spool visibility', () => {
  24. beforeEach(() => {
  25. store = {};
  26. vi.mocked(localStorage.getItem).mockImplementation((key: string) => store[key] ?? null);
  27. vi.mocked(localStorage.setItem).mockImplementation((key: string, value: string) => {
  28. store[key] = String(value);
  29. });
  30. });
  31. afterEach(() => {
  32. vi.mocked(localStorage.getItem).mockReset();
  33. vi.mocked(localStorage.setItem).mockReset();
  34. });
  35. it('defaults to visible for a printer that was never toggled', () => {
  36. expect(isExternalSpoolHidden(1)).toBe(false);
  37. });
  38. it('round-trips the hidden flag through localStorage', () => {
  39. setExternalSpoolHidden(7, true);
  40. expect(isExternalSpoolHidden(7)).toBe(true);
  41. expect(stored()).toEqual({ '7': true });
  42. });
  43. it('keeps each printer independent', () => {
  44. setExternalSpoolHidden(1, true);
  45. expect(isExternalSpoolHidden(1)).toBe(true);
  46. expect(isExternalSpoolHidden(2)).toBe(false);
  47. // Hiding a second printer must not disturb the first — the writer
  48. // re-reads before merging rather than overwriting the whole object.
  49. setExternalSpoolHidden(2, true);
  50. expect(isExternalSpoolHidden(1)).toBe(true);
  51. expect(isExternalSpoolHidden(2)).toBe(true);
  52. });
  53. it('drops the key when shown again rather than storing false', () => {
  54. setExternalSpoolHidden(3, true);
  55. setExternalSpoolHidden(3, false);
  56. expect(isExternalSpoolHidden(3)).toBe(false);
  57. // Otherwise the object grows an entry for every printer ever toggled twice.
  58. expect(stored()).toEqual({});
  59. });
  60. it('treats malformed stored values as "nothing hidden"', () => {
  61. for (const junk of ['not json', 'null', '"a string"', '[1,2,3]', '42']) {
  62. store[KEY] = junk;
  63. expect(isExternalSpoolHidden(1)).toBe(false);
  64. }
  65. });
  66. it('recovers from a malformed store on the next write', () => {
  67. store[KEY] = '[1,2,3]';
  68. setExternalSpoolHidden(5, true);
  69. expect(isExternalSpoolHidden(5)).toBe(true);
  70. expect(stored()).toEqual({ '5': true });
  71. });
  72. it('survives localStorage being unavailable', () => {
  73. vi.mocked(localStorage.getItem).mockImplementation(() => {
  74. throw new Error('SecurityError: access denied');
  75. });
  76. vi.mocked(localStorage.setItem).mockImplementation(() => {
  77. throw new Error('QuotaExceededError');
  78. });
  79. // Private-mode browsers throw on both. Neither may escape into a render.
  80. expect(() => isExternalSpoolHidden(1)).not.toThrow();
  81. expect(isExternalSpoolHidden(1)).toBe(false);
  82. expect(() => setExternalSpoolHidden(1, true)).not.toThrow();
  83. });
  84. });