@nordcraft/search 1.0.45 → 1.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/problems.worker.js +11 -3
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/attributes/unknownComponentAttributeRule.js +33 -0
  4. package/dist/rules/attributes/unknownComponentAttributeRule.js.map +1 -0
  5. package/dist/rules/attributes/unknownComponentAttributeRule.test.js +145 -0
  6. package/dist/rules/attributes/unknownComponentAttributeRule.test.js.map +1 -0
  7. package/dist/rules/logic/noStaticNodeCondition.js +29 -0
  8. package/dist/rules/logic/noStaticNodeCondition.js.map +1 -0
  9. package/dist/rules/logic/noStaticNodeCondition.test.js +274 -0
  10. package/dist/rules/logic/noStaticNodeCondition.test.js.map +1 -0
  11. package/dist/rules/logic/noUnnecessaryConditionFalsy.js +5 -1
  12. package/dist/rules/logic/noUnnecessaryConditionFalsy.js.map +1 -1
  13. package/dist/rules/logic/noUnnecessaryConditionTruthy.js +8 -4
  14. package/dist/rules/logic/noUnnecessaryConditionTruthy.js.map +1 -1
  15. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js +33 -4
  16. package/dist/rules/logic/noUnnecessaryConditionTruthy.test.js.map +1 -1
  17. package/dist/rules/noReferenceNodeRule.js +10 -8
  18. package/dist/rules/noReferenceNodeRule.js.map +1 -1
  19. package/dist/rules/noReferenceNodeRule.test.js +1 -1
  20. package/dist/rules/style/invalidStyleSyntaxRule.js +2 -2
  21. package/dist/rules/style/invalidStyleSyntaxRule.test.js +1 -1
  22. package/dist/rules/workflows/noPostNavigateAction.js +13 -2
  23. package/dist/rules/workflows/noPostNavigateAction.js.map +1 -1
  24. package/dist/rules/workflows/noPostNavigateAction.test.js +102 -1
  25. package/dist/rules/workflows/noPostNavigateAction.test.js.map +1 -1
  26. package/dist/util/contextlessEvaluateFormula.js +77 -0
  27. package/dist/util/contextlessEvaluateFormula.js.map +1 -0
  28. package/dist/util/contextlessEvaluateFormula.test.js +152 -0
  29. package/dist/util/contextlessEvaluateFormula.test.js.map +1 -0
  30. package/dist/util/helpers.test.js +1 -1
  31. package/dist/util/helpers.test.js.map +1 -1
  32. package/dist/util/removeUnused.fix.js +18 -1
  33. package/dist/util/removeUnused.fix.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/problems.worker.ts +18 -5
  36. package/src/rules/attributes/unknownComponentAttributeRule.test.ts +156 -0
  37. package/src/rules/attributes/unknownComponentAttributeRule.ts +40 -0
  38. package/src/rules/logic/noStaticNodeCondition.test.ts +290 -0
  39. package/src/rules/logic/noStaticNodeCondition.ts +43 -0
  40. package/src/rules/logic/noUnnecessaryConditionFalsy.ts +5 -4
  41. package/src/rules/logic/noUnnecessaryConditionTruthy.test.ts +37 -4
  42. package/src/rules/logic/noUnnecessaryConditionTruthy.ts +10 -8
  43. package/src/rules/noReferenceNodeRule.test.ts +1 -1
  44. package/src/rules/noReferenceNodeRule.ts +17 -10
  45. package/src/rules/style/invalidStyleSyntaxRule.test.ts +1 -1
  46. package/src/rules/style/invalidStyleSyntaxRule.ts +3 -3
  47. package/src/rules/workflows/noPostNavigateAction.test.ts +111 -1
  48. package/src/rules/workflows/noPostNavigateAction.ts +20 -3
  49. package/src/types.d.ts +12 -0
  50. package/src/util/contextlessEvaluateFormula.test.ts +190 -0
  51. package/src/util/contextlessEvaluateFormula.ts +110 -0
  52. package/src/util/helpers.test.ts +1 -1
  53. package/src/util/removeUnused.fix.ts +29 -1
@@ -1,4 +1,5 @@
1
1
  import type { Rule } from '../../types'
2
+ import { contextlessEvaluateFormula } from '../../util/contextlessEvaluateFormula'
2
3
 
3
4
  export const noUnnecessaryConditionFalsy: Rule = {
4
5
  code: 'no-unnecessary-condition-falsy',
@@ -10,10 +11,10 @@ export const noUnnecessaryConditionFalsy: Rule = {
10
11
  }
11
12
 
12
13
  if (
13
- value.arguments.some(
14
- (arg) =>
15
- arg.formula.type === 'value' && Boolean(arg.formula.value) === false,
16
- )
14
+ value.arguments.some((arg) => {
15
+ const { result, isStatic } = contextlessEvaluateFormula(arg.formula)
16
+ return isStatic && Boolean(result) === false
17
+ })
17
18
  ) {
18
19
  report(path)
19
20
  }
@@ -32,7 +32,41 @@ describe('noUnnecessaryConditionTruthy', () => {
32
32
  },
33
33
  ],
34
34
  },
35
- test2: {
35
+ },
36
+ classes: {},
37
+ events: {},
38
+ tag: 'div',
39
+ children: [],
40
+ style: {},
41
+ },
42
+ },
43
+ formulas: {},
44
+ apis: {},
45
+ attributes: {},
46
+ variables: {},
47
+ },
48
+ },
49
+ },
50
+ rules: [noUnnecessaryConditionTruthy],
51
+ }),
52
+ )
53
+
54
+ expect(problems).toHaveLength(1)
55
+ expect(problems[0].code).toBe('no-unnecessary-condition-truthy')
56
+ })
57
+
58
+ test('should report unnecessary truthy conditions when a value is of type object or array as they are always consider truthy', () => {
59
+ const problems = Array.from(
60
+ searchProject({
61
+ files: {
62
+ components: {
63
+ test: {
64
+ name: 'test',
65
+ nodes: {
66
+ root: {
67
+ type: 'element',
68
+ attrs: {
69
+ test1: {
36
70
  type: 'or',
37
71
  arguments: [
38
72
  {
@@ -43,7 +77,7 @@ describe('noUnnecessaryConditionTruthy', () => {
43
77
  },
44
78
  ],
45
79
  },
46
- test3: {
80
+ test2: {
47
81
  type: 'or',
48
82
  arguments: [
49
83
  {
@@ -73,10 +107,9 @@ describe('noUnnecessaryConditionTruthy', () => {
73
107
  }),
74
108
  )
75
109
 
76
- expect(problems).toHaveLength(3)
110
+ expect(problems).toHaveLength(2)
77
111
  expect(problems[0].code).toBe('no-unnecessary-condition-truthy')
78
112
  expect(problems[1].code).toBe('no-unnecessary-condition-truthy')
79
- expect(problems[2].code).toBe('no-unnecessary-condition-truthy')
80
113
  })
81
114
 
82
115
  test('should not report necessary truthy conditions', () => {
@@ -1,4 +1,5 @@
1
1
  import type { Rule } from '../../types'
2
+ import { contextlessEvaluateFormula } from '../../util/contextlessEvaluateFormula'
2
3
 
3
4
  export const noUnnecessaryConditionTruthy: Rule = {
4
5
  code: 'no-unnecessary-condition-truthy',
@@ -10,14 +11,15 @@ export const noUnnecessaryConditionTruthy: Rule = {
10
11
  }
11
12
 
12
13
  if (
13
- value.arguments.some(
14
- (arg) =>
15
- (arg.formula.type === 'value' &&
16
- Boolean(arg.formula.value) === true) ||
17
- // Objects and arrays, even empty ones, are always truthy
18
- arg.formula.type === 'object' ||
19
- arg.formula.type === 'array',
20
- )
14
+ value.arguments.some((arg) => {
15
+ // Objects and arrays, even empty ones, are always truthy
16
+ if (arg.formula.type === 'object' || arg.formula.type === 'array') {
17
+ return true
18
+ }
19
+
20
+ const { result, isStatic } = contextlessEvaluateFormula(arg.formula)
21
+ return isStatic && Boolean(result) === true
22
+ })
21
23
  ) {
22
24
  report(path)
23
25
  }
@@ -130,7 +130,7 @@ describe('fix noReferenceNodeRule', () => {
130
130
  const fixedFiles = fixProject({
131
131
  files,
132
132
  rule: noReferenceNodeRule,
133
- fixType: 'delete orphan node',
133
+ fixType: 'delete-orphan-node',
134
134
  })
135
135
  expect(Object.keys(fixedFiles.components.test!.nodes)).toEqual([
136
136
  'root',
@@ -6,22 +6,29 @@ export const noReferenceNodeRule: Rule<{ node: string }> = {
6
6
  level: 'warning',
7
7
  category: 'No References',
8
8
  visit: (report, args) => {
9
- if (args.nodeType !== 'component') {
9
+ if (args.nodeType !== 'component-node') {
10
10
  return
11
11
  }
12
- const { path, value: component } = args
13
- const referencedNodes = new Set(
14
- Object.values(component.nodes).flatMap((node) => node.children ?? []),
12
+ const { path, component } = args
13
+ const nodeId = path.at(-1)
14
+ if (typeof nodeId !== 'string') {
15
+ return
16
+ }
17
+ const referencedNodesInComponent = args.memo(
18
+ `node-references-${component.name}`,
19
+ () =>
20
+ new Set(
21
+ Object.values(component.nodes).flatMap((node) => node.children ?? []),
22
+ ),
15
23
  )
16
- for (const key of Object.keys(component.nodes)) {
17
- if (key !== 'root' && !referencedNodes.has(key)) {
18
- report([...path, 'nodes', key], { node: key }, ['delete orphan node'])
19
- }
24
+
25
+ if (nodeId !== 'root' && !referencedNodesInComponent.has(nodeId)) {
26
+ report(path, { node: nodeId }, ['delete-orphan-node'])
20
27
  }
21
28
  },
22
29
  fixes: {
23
- 'delete orphan node': removeFromPathFix,
30
+ 'delete-orphan-node': removeFromPathFix,
24
31
  },
25
32
  }
26
33
 
27
- export type NoReferenceNodeRuleFix = 'delete orphan node'
34
+ export type NoReferenceNodeRuleFix = 'delete-orphan-node'
@@ -92,7 +92,7 @@ describe('fix invalidStyleSyntaxRule', () => {
92
92
  const fixedFiles = fixProject({
93
93
  files,
94
94
  rule: invalidStyleSyntaxRule,
95
- fixType: 'delete style property',
95
+ fixType: 'delete-style-property',
96
96
  })
97
97
  expect((fixedFiles.components.test!.nodes.root as ElementNodeModel).style)
98
98
  .toMatchInlineSnapshot(`
@@ -24,12 +24,12 @@ export const invalidStyleSyntaxRule: Rule<{
24
24
  },
25
25
  )
26
26
  if (!valid) {
27
- report(path, { property: value.styleProperty }, ['delete style property'])
27
+ report(path, { property: value.styleProperty }, ['delete-style-property'])
28
28
  }
29
29
  },
30
30
  fixes: {
31
- 'delete style property': removeFromPathFix,
31
+ 'delete-style-property': removeFromPathFix,
32
32
  },
33
33
  }
34
34
 
35
- export type InvalidStyleSyntaxRuleFix = 'delete style property'
35
+ export type InvalidStyleSyntaxRuleFix = 'delete-style-property'
@@ -1,8 +1,11 @@
1
+ import type { ElementNodeModel } from '@nordcraft/core/dist/component/component.types'
2
+ import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
1
3
  import { describe, expect, test } from 'bun:test'
4
+ import { fixProject } from '../../fixProject'
2
5
  import { searchProject } from '../../searchProject'
3
6
  import { noPostNavigateAction } from './noPostNavigateAction'
4
7
 
5
- describe('noPostNavigateAction', () => {
8
+ describe('find noPostNavigateAction', () => {
6
9
  test('should detect actions after a url navigate action', () => {
7
10
  const problems = Array.from(
8
11
  searchProject({
@@ -457,3 +460,110 @@ describe('noPostNavigateAction', () => {
457
460
  expect(problems).toHaveLength(0)
458
461
  })
459
462
  })
463
+
464
+ describe('fix noPostNavigateAction', () => {
465
+ test('should remove actions after a url navigate action', () => {
466
+ const files: ProjectFiles = {
467
+ formulas: {},
468
+ components: {
469
+ nav: {
470
+ attributes: {},
471
+ events: [],
472
+ formulas: {},
473
+ nodes: {
474
+ root: {
475
+ type: 'element',
476
+ tag: 'div',
477
+ attrs: {},
478
+ classes: {},
479
+ events: {},
480
+ children: ['wkPXF99C3qdRgZmUcnHO_'],
481
+ style: {},
482
+ },
483
+ wkPXF99C3qdRgZmUcnHO_: {
484
+ tag: 'button',
485
+ type: 'element',
486
+ attrs: {},
487
+ style: {
488
+ color: 'var(--grey-200, #E5E5E5)',
489
+ 'border-radius': '6px',
490
+ 'background-color': 'var(--blue-600, #2563EB)',
491
+ 'padding-left': '8px',
492
+ 'padding-right': '8px',
493
+ 'padding-top': '8px',
494
+ 'padding-bottom': '8px',
495
+ width: 'fit-content',
496
+ cursor: 'pointer',
497
+ },
498
+ events: {
499
+ click: {
500
+ trigger: 'click',
501
+ actions: [
502
+ {
503
+ name: '@toddle/gotToURL',
504
+ arguments: [
505
+ {
506
+ name: 'URL',
507
+ formula: {
508
+ type: 'value',
509
+ value: 'https://example.com',
510
+ },
511
+ },
512
+ ],
513
+ label: 'Go to URL',
514
+ },
515
+ {
516
+ name: '@toddle/logToConsole',
517
+ arguments: [
518
+ {
519
+ name: 'Label',
520
+ formula: {
521
+ type: 'value',
522
+ value: '',
523
+ },
524
+ },
525
+ {
526
+ name: 'Data',
527
+ formula: {
528
+ type: 'value',
529
+ value: '<Data>',
530
+ },
531
+ },
532
+ ],
533
+ label: 'Log to console',
534
+ },
535
+ ],
536
+ },
537
+ },
538
+ classes: {},
539
+ children: ['zFhmZR6YnLy089HmlK-Yn'],
540
+ variants: [],
541
+ },
542
+ 'zFhmZR6YnLy089HmlK-Yn': {
543
+ type: 'text',
544
+ value: {
545
+ type: 'value',
546
+ value: 'Button',
547
+ },
548
+ },
549
+ },
550
+ variables: {},
551
+ apis: {},
552
+ name: 'nav',
553
+ },
554
+ },
555
+ }
556
+ const fixedProject = fixProject({
557
+ files,
558
+ rule: noPostNavigateAction,
559
+ fixType: 'delete-following-actions',
560
+ })
561
+ expect(
562
+ (
563
+ fixedProject.components.nav?.nodes[
564
+ 'wkPXF99C3qdRgZmUcnHO_'
565
+ ] as ElementNodeModel
566
+ )?.events?.click?.actions,
567
+ ).toHaveLength(1)
568
+ })
569
+ })
@@ -1,5 +1,5 @@
1
- import { get } from '@nordcraft/core/dist/utils/collections'
2
- import type { Rule } from '../../types'
1
+ import { get, set } from '@nordcraft/core/dist/utils/collections'
2
+ import type { ActionModelNode, Rule } from '../../types'
3
3
 
4
4
  export const noPostNavigateAction: Rule<{ parameter: string }> = {
5
5
  code: 'no post navigate action',
@@ -25,7 +25,24 @@ export const noPostNavigateAction: Rule<{ parameter: string }> = {
25
25
  const actionIndex = Number(_actionIndex)
26
26
  if (actionIndex < actions.length - 1) {
27
27
  // If the action is not the last one in the array, report it
28
- report(path)
28
+ report(path, undefined, ['delete-following-actions'])
29
29
  }
30
30
  },
31
+ fixes: {
32
+ 'delete-following-actions': ({ path, files }: ActionModelNode) => {
33
+ const actionsArrayPath = path.slice(0, -1).map((p) => String(p))
34
+ const actions = get(files, actionsArrayPath)
35
+ const actionIndex = path.at(-1)
36
+ if (actionIndex === undefined || !Array.isArray(actions)) {
37
+ return
38
+ }
39
+ return set(
40
+ files,
41
+ actionsArrayPath,
42
+ actions.slice(0, Number(actionIndex) + 1),
43
+ )
44
+ },
45
+ },
31
46
  }
47
+
48
+ export type NoPostNavigateActionRuleFix = 'delete-following-actions'
package/src/types.d.ts CHANGED
@@ -25,13 +25,16 @@ import type { LegacyActionRuleFix } from './rules/actions/legacyActionRule'
25
25
  import type { NoReferenceProjectActionRuleFix } from './rules/actions/noReferenceProjectActionRule'
26
26
  import type { NoReferenceApiRuleFix } from './rules/apis/noReferenceApiRule'
27
27
  import type { NoReferenceAttributeRuleFix } from './rules/attributes/noReferenceAttributeRule'
28
+ import type { UnknownComponentAttributeRuleFix } from './rules/attributes/unknownComponentAttributeRule'
28
29
  import type { NoReferenceComponentRuleFix } from './rules/components/noReferenceComponentRule'
29
30
  import type { NoReferenceEventRuleFix } from './rules/events/noReferenceEventRule'
30
31
  import type { LegacyFormulaRuleFix } from './rules/formulas/legacyFormulaRule'
31
32
  import type { NoReferenceComponentFormulaRuleFix } from './rules/formulas/noReferenceComponentFormulaRule'
32
33
  import type { NoReferenceProjectFormulaRuleFix } from './rules/formulas/noReferenceProjectFormulaRule'
34
+ import type { NoStaticNodeConditionRuleFix } from './rules/logic/noStaticNodeCondition'
33
35
  import type { NoReferenceNodeRuleFix } from './rules/noReferenceNodeRule'
34
36
  import type { InvalidStyleSyntaxRuleFix } from './rules/style/invalidStyleSyntaxRule'
37
+ import type { NoPostNavigateActionRuleFix } from './rules/workflows/noPostNavigateAction'
35
38
 
36
39
  type Code =
37
40
  | 'duplicate event trigger'
@@ -59,6 +62,7 @@ type Code =
59
62
  | 'no-reference project action'
60
63
  | 'no-reference project formula'
61
64
  | 'no-reference variable'
65
+ | 'no-static-node-condition'
62
66
  | 'no-unnecessary-condition-falsy'
63
67
  | 'no-unnecessary-condition-truthy'
64
68
  | 'non-empty void element'
@@ -73,6 +77,7 @@ type Code =
73
77
  | 'unknown api'
74
78
  | 'unknown attribute'
75
79
  | 'unknown classname'
80
+ | 'unknown component attribute'
76
81
  | 'unknown component formula input'
77
82
  | 'unknown component slot'
78
83
  | 'unknown context formula'
@@ -217,6 +222,10 @@ type ComponentWorkflowNode = {
217
222
  testValue: any
218
223
  }[]
219
224
  | null
225
+ callbacks?: {
226
+ name: string
227
+ testValue: any
228
+ }[]
220
229
  actions: ActionModel[]
221
230
  exposeInContext?: boolean | undefined
222
231
  }
@@ -343,6 +352,9 @@ type FixType =
343
352
  | NoReferenceComponentFormulaRuleFix
344
353
  | NoReferenceNodeRuleFix
345
354
  | InvalidStyleSyntaxRuleFix
355
+ | NoStaticNodeConditionRuleFix
356
+ | NoPostNavigateActionRuleFix
357
+ | UnknownComponentAttributeRuleFix
346
358
 
347
359
  export interface Rule<T = unknown, V = NodeType> {
348
360
  category: Category
@@ -0,0 +1,190 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { contextlessEvaluateFormula } from './contextlessEvaluateFormula'
3
+
4
+ describe('contextlessEvaluateFormula', () => {
5
+ test('should return `true` when the formula is a simple value formula', () => {
6
+ expect(
7
+ contextlessEvaluateFormula({
8
+ type: 'value',
9
+ value: true,
10
+ }),
11
+ ).toEqual({
12
+ isStatic: true,
13
+ result: true,
14
+ })
15
+ })
16
+
17
+ test('should not return a result and have `isStatic: false` when the formula uses a non-pure formula ("randomNumber", "Now")', () => {
18
+ expect(
19
+ contextlessEvaluateFormula({
20
+ type: 'apply',
21
+ name: 'randomNumber',
22
+ arguments: [],
23
+ }),
24
+ ).toEqual({
25
+ isStatic: false,
26
+ result: undefined,
27
+ })
28
+
29
+ expect(
30
+ contextlessEvaluateFormula({
31
+ type: 'apply',
32
+ name: 'now',
33
+ arguments: [],
34
+ }),
35
+ ).toEqual({
36
+ isStatic: false,
37
+ result: undefined,
38
+ })
39
+ })
40
+
41
+ test('should not return a result and have `isStatic: false` when the formula depends on a variable', () => {
42
+ expect(
43
+ contextlessEvaluateFormula({
44
+ type: 'path',
45
+ path: ['Variables', 'myVariable'],
46
+ }),
47
+ ).toEqual({
48
+ isStatic: false,
49
+ result: undefined,
50
+ })
51
+ })
52
+
53
+ test('should return a result and static for an array formula where all args are array or value types', () => {
54
+ expect(
55
+ contextlessEvaluateFormula({
56
+ type: 'array',
57
+ arguments: [
58
+ { formula: { type: 'value', value: 1 } },
59
+ {
60
+ formula: {
61
+ type: 'array',
62
+ arguments: [{ formula: { type: 'value', value: 2 } }],
63
+ },
64
+ },
65
+ ],
66
+ }),
67
+ ).toEqual({
68
+ isStatic: true,
69
+ result: [1, [2]],
70
+ })
71
+ })
72
+
73
+ test('should return `isStatic: false` for an array formula with a non-static argument', () => {
74
+ expect(
75
+ contextlessEvaluateFormula({
76
+ type: 'array',
77
+ arguments: [
78
+ { formula: { type: 'value', value: 1 } },
79
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
80
+ ],
81
+ }),
82
+ ).toEqual({
83
+ isStatic: false,
84
+ result: [
85
+ 1,
86
+ undefined, // The second argument was not static
87
+ ],
88
+ })
89
+ })
90
+
91
+ test('should be static true for `And` formulas with no arguments', () => {
92
+ expect(
93
+ contextlessEvaluateFormula({
94
+ type: 'and',
95
+ arguments: [],
96
+ }),
97
+ ).toEqual({
98
+ isStatic: true,
99
+ result: true,
100
+ })
101
+ })
102
+
103
+ test('should be static false for `Or` formulas with no arguments', () => {
104
+ expect(
105
+ contextlessEvaluateFormula({
106
+ type: 'or',
107
+ arguments: [],
108
+ }),
109
+ ).toEqual({
110
+ isStatic: true,
111
+ result: false,
112
+ })
113
+ })
114
+
115
+ test('should be static true for `And` formulas with all static truthy arguments', () => {
116
+ expect(
117
+ contextlessEvaluateFormula({
118
+ type: 'and',
119
+ arguments: [
120
+ { formula: { type: 'value', value: true } },
121
+ { formula: { type: 'value', value: true } },
122
+ ],
123
+ }),
124
+ ).toEqual({
125
+ isStatic: true,
126
+ result: true,
127
+ })
128
+ })
129
+
130
+ test('should not be static for `And` where any dynamic value exist', () => {
131
+ expect(
132
+ contextlessEvaluateFormula({
133
+ type: 'and',
134
+ arguments: [
135
+ { formula: { type: 'value', value: true } },
136
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
137
+ ],
138
+ }),
139
+ ).toEqual({
140
+ isStatic: false,
141
+ result: undefined,
142
+ })
143
+ })
144
+
145
+ test('should be static false for `And` when any argument is static false, even when dynamic arguments exist', () => {
146
+ expect(
147
+ contextlessEvaluateFormula({
148
+ type: 'and',
149
+ arguments: [
150
+ { formula: { type: 'value', value: true } },
151
+ { formula: { type: 'path', path: ['Variables', 'myVariable'] } },
152
+ { formula: { type: 'value', value: false } },
153
+ ],
154
+ }),
155
+ ).toEqual({
156
+ isStatic: true,
157
+ result: false,
158
+ })
159
+ })
160
+
161
+ test('should not be static for `Or` when any argument is dynamic and all static are falsy', () => {
162
+ expect(
163
+ contextlessEvaluateFormula({
164
+ type: 'or',
165
+ arguments: [
166
+ { formula: { type: 'value', value: false } },
167
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
168
+ ],
169
+ }),
170
+ ).toEqual({
171
+ isStatic: false,
172
+ result: undefined,
173
+ })
174
+ })
175
+
176
+ test('should be static true for `Or` when any argument is static true, even when dynamic arguments exist', () => {
177
+ expect(
178
+ contextlessEvaluateFormula({
179
+ type: 'or',
180
+ arguments: [
181
+ { formula: { type: 'value', value: true } },
182
+ { formula: { type: 'apply', name: 'randomNumber', arguments: [] } },
183
+ ],
184
+ }),
185
+ ).toEqual({
186
+ isStatic: true,
187
+ result: true,
188
+ })
189
+ })
190
+ })