locationSensorAlertDefaults.test.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. /**
  2. * The per-category alert defaults are server-backed (#2824 review round 4).
  3. *
  4. * They seed the alert rule written onto each sensor row when one is bound, so
  5. * two admins binding sensors from different browsers must not seed different
  6. * rules, and a backup/restore has to carry them. Only `showOnCard` stays in
  7. * localStorage: show_on_card is decided per sensor and this is nothing more
  8. * than the form's pre-selection.
  9. */
  10. import { describe, it, expect, beforeEach, vi } from 'vitest';
  11. import {
  12. defaultLocationSensorDefaults,
  13. loadLocationSensorDefaults,
  14. saveLocationSensorShowOnCardDefaults,
  15. serializeLocationSensorAlertDefaults,
  16. } from '../../utils/locationSensorDefaults';
  17. beforeEach(() => {
  18. vi.mocked(localStorage.getItem).mockReset();
  19. vi.mocked(localStorage.setItem).mockReset();
  20. });
  21. describe('location sensor alert defaults — server-backed', () => {
  22. it('falls back to the built-ins when the setting is empty', () => {
  23. expect(loadLocationSensorDefaults('')).toEqual(defaultLocationSensorDefaults());
  24. expect(loadLocationSensorDefaults(undefined)).toEqual(defaultLocationSensorDefaults());
  25. expect(loadLocationSensorDefaults(null)).toEqual(defaultLocationSensorDefaults());
  26. });
  27. it('takes the alert fields from the server value', () => {
  28. const json = JSON.stringify({
  29. humidity: { alertAbove: '45', alertBelow: '15', notifyOnAlert: true },
  30. });
  31. const defaults = loadLocationSensorDefaults(json);
  32. expect(defaults.humidity.alertAbove).toBe('45');
  33. expect(defaults.humidity.alertBelow).toBe('15');
  34. expect(defaults.humidity.notifyOnAlert).toBe(true);
  35. // Categories absent from the setting keep their built-ins.
  36. expect(defaults.temperature).toEqual(defaultLocationSensorDefaults().temperature);
  37. });
  38. it('round-trips through serialize', () => {
  39. const original = defaultLocationSensorDefaults();
  40. original.temperature.alertAbove = '35';
  41. original.battery.notifyOnAlert = true;
  42. const reloaded = loadLocationSensorDefaults(serializeLocationSensorAlertDefaults(original));
  43. expect(reloaded.temperature.alertAbove).toBe('35');
  44. expect(reloaded.battery.notifyOnAlert).toBe(true);
  45. });
  46. it('never puts showOnCard in the server value — it is per sensor, not per installation', () => {
  47. const defaults = defaultLocationSensorDefaults();
  48. defaults.humidity.showOnCard = false;
  49. const parsed = JSON.parse(serializeLocationSensorAlertDefaults(defaults));
  50. expect(parsed.humidity).not.toHaveProperty('showOnCard');
  51. expect(Object.keys(parsed.humidity).sort()).toEqual(['alertAbove', 'alertBelow', 'notifyOnAlert']);
  52. });
  53. // setup.ts replaces localStorage with bare vi.fn() stubs that store nothing,
  54. // so these drive it explicitly rather than round-tripping through it.
  55. it('writes only the per-category booleans to localStorage', () => {
  56. const defaults = defaultLocationSensorDefaults();
  57. defaults.humidity.showOnCard = false;
  58. saveLocationSensorShowOnCardDefaults(defaults);
  59. expect(localStorage.setItem).toHaveBeenCalledWith(
  60. 'bambuddy-location-sensor-show-on-card-defaults',
  61. JSON.stringify({ temperature: true, humidity: false, battery: true })
  62. );
  63. });
  64. it('applies the stored showOnCard over the built-in, independent of the server value', () => {
  65. vi.mocked(localStorage.getItem).mockReturnValue(JSON.stringify({ humidity: false }));
  66. const reloaded = loadLocationSensorDefaults(
  67. serializeLocationSensorAlertDefaults(defaultLocationSensorDefaults())
  68. );
  69. expect(reloaded.humidity.showOnCard).toBe(false);
  70. expect(reloaded.temperature.showOnCard).toBe(true);
  71. });
  72. it('survives a corrupted setting instead of blocking the dialog', () => {
  73. expect(loadLocationSensorDefaults('not json at all')).toEqual(defaultLocationSensorDefaults());
  74. });
  75. it('ignores unexpected keys and wrong types in the stored value', () => {
  76. const json = JSON.stringify({
  77. humidity: { alertAbove: 45, notifyOnAlert: 'yes', showOnCard: false, injected: 'x' },
  78. });
  79. const defaults = loadLocationSensorDefaults(json);
  80. // Wrong types are rejected, built-ins survive.
  81. expect(defaults.humidity.alertAbove).toBe(defaultLocationSensorDefaults().humidity.alertAbove);
  82. expect(defaults.humidity.notifyOnAlert).toBe(false);
  83. // showOnCard from the server value must not win over the local preference.
  84. expect(defaults.humidity.showOnCard).toBe(true);
  85. expect(defaults.humidity).not.toHaveProperty('injected');
  86. });
  87. });