ui-conditions.test.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. /**
  2. * Conditions as a reader says them: the joins we add (`&&` between guards,
  3. * `||` between a link's scenarios, `!(…)` around a negated guard) become
  4. * and / or / not, the code inside a guard stays code, and a link with several
  5. * call sites is several scenarios with their shared clauses said once.
  6. */
  7. import { describe, it, expect } from 'vitest';
  8. import { clauseWords, clauses, restWords, scenarios, splitTop, whenWords } from '../ui/src/lib/conditions';
  9. describe('conditions', () => {
  10. it('splits at the top level only, respecting brackets and strings', () => {
  11. expect(splitTop('a && (b || c) && "x && y" && d', ' && ')).toEqual(['a', '(b || c)', '"x && y"', 'd']);
  12. expect(splitTop('a && b || c && d', ' || ')).toEqual(['a && b', 'c && d']);
  13. expect(clauses('!busy && isCollected')).toEqual(['!busy', 'isCollected']);
  14. // A merged condition has no single innermost clause: it comes back whole.
  15. expect(clauses('a && b || c')).toEqual(['a && b || c']);
  16. });
  17. it('says NOT for our negations and leaves the code inside alone', () => {
  18. expect(clauseWords('!busy')).toBe('NOT busy');
  19. expect(clauseWords('!user?.organization_id')).toBe('NOT user?.organization_id');
  20. expect(clauseWords('!(isUploadInProgress || elapsed < 5000)')).toBe('NOT (isUploadInProgress || elapsed < 5000)');
  21. // `!(a) || b` is not a negated whole: untouched.
  22. expect(clauseWords('!(a) || b')).toBe('!(a) || b');
  23. expect(clauseWords('(!object?.id || !object?.name)')).toBe('(!object?.id || !object?.name)');
  24. expect(clauseWords('selectedDetectionItems.length === 1')).toBe('selectedDetectionItems.length === 1');
  25. });
  26. it('words a whole condition: AND within a scenario, OR between scenarios', () => {
  27. expect(whenWords('!(busy || late) && user?.organization_id && !object?.id')).toBe(
  28. 'NOT (busy || late) AND user?.organization_id AND NOT object?.id'
  29. );
  30. expect(whenWords('!x && y || !x && !y')).toBe('NOT x AND y OR NOT x AND NOT y');
  31. // The same guard met twice along a chain is said once.
  32. expect(whenWords('ctl && !(!ctl || done) && !(!ctl || done) && ready')).toBe('ctl AND NOT (!ctl || done) AND ready');
  33. expect(scenarios([{ when: 'a && a && b' }]).common).toEqual(['a', 'b']);
  34. expect(whenWords('')).toBe('');
  35. });
  36. it('factors the clauses every scenario shares, and keeps each row’s own tail', () => {
  37. const sites = [
  38. { line: 248, when: '!(busy || late) && !user?.organization_id' },
  39. { line: 257, when: '!(busy || late) && user?.organization_id && (!object?.id || !object?.name)' },
  40. { line: 292, when: '!(busy || late) && user?.organization_id && !(!object?.id || !object?.name) && items.length === 1' },
  41. { line: 306, when: '!(busy || late) && user?.organization_id && !(!object?.id || !object?.name) && !items.length' },
  42. ];
  43. const sc = scenarios(sites);
  44. expect(sc.common).toEqual(['!(busy || late)']);
  45. expect(sc.rows.map((r) => r.rest)).toEqual([
  46. ['!user?.organization_id'],
  47. ['user?.organization_id', '(!object?.id || !object?.name)'],
  48. ['user?.organization_id', '!(!object?.id || !object?.name)', 'items.length === 1'],
  49. ['user?.organization_id', '!(!object?.id || !object?.name)', '!items.length'],
  50. ]);
  51. expect(restWords(sc.rows[0]!.rest, true)).toBe('AND NOT user?.organization_id');
  52. expect(restWords(sc.rows[2]!.rest, true)).toBe(
  53. 'AND user?.organization_id AND NOT (!object?.id || !object?.name) AND items.length === 1'
  54. );
  55. });
  56. it('one site is one scenario with nothing left to say; no shared prefix says when', () => {
  57. expect(scenarios([{ when: 'a && b' }])).toEqual({ common: ['a', 'b'], rows: [{ site: { when: 'a && b' }, rest: [] }] });
  58. const sc = scenarios([{ when: 'a' }, { when: 'b' }, { when: '' }]);
  59. expect(sc.common).toEqual([]);
  60. expect(restWords(sc.rows[0]!.rest, false)).toBe('WHEN a');
  61. expect(restWords(sc.rows[2]!.rest, false)).toBe('always');
  62. expect(scenarios([])).toEqual({ common: [], rows: [] });
  63. });
  64. });