@nordcraft/search 1.0.44 → 1.0.46

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 (59) hide show
  1. package/dist/problems.worker.js +13 -5
  2. package/dist/problems.worker.js.map +1 -1
  3. package/dist/rules/actions/legacyActionRule.test.js +3 -3
  4. package/dist/rules/actions/legacyActionRule.test.js.map +1 -1
  5. package/dist/rules/events/duplicateEventTriggerRule.js +2 -1
  6. package/dist/rules/events/duplicateEventTriggerRule.js.map +1 -1
  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 +24 -0
  18. package/dist/rules/noReferenceNodeRule.js.map +1 -0
  19. package/dist/rules/noReferenceNodeRule.test.js +131 -0
  20. package/dist/rules/noReferenceNodeRule.test.js.map +1 -0
  21. package/dist/rules/style/invalidStyleSyntaxRule.js +28 -0
  22. package/dist/rules/style/invalidStyleSyntaxRule.js.map +1 -0
  23. package/dist/rules/style/invalidStyleSyntaxRule.test.js +100 -0
  24. package/dist/rules/style/invalidStyleSyntaxRule.test.js.map +1 -0
  25. package/dist/searchProject.js +22 -1
  26. package/dist/searchProject.js.map +1 -1
  27. package/dist/util/contextlessEvaluateFormula.js +77 -0
  28. package/dist/util/contextlessEvaluateFormula.js.map +1 -0
  29. package/dist/util/contextlessEvaluateFormula.test.js +152 -0
  30. package/dist/util/contextlessEvaluateFormula.test.js.map +1 -0
  31. package/dist/util/removeUnused.fix.js +18 -1
  32. package/dist/util/removeUnused.fix.js.map +1 -1
  33. package/package.json +4 -3
  34. package/src/problems.worker.ts +20 -7
  35. package/src/rules/actions/legacyActionRule.test.ts +3 -3
  36. package/src/rules/events/duplicateEventTriggerRule.ts +2 -1
  37. package/src/rules/logic/noStaticNodeCondition.test.ts +290 -0
  38. package/src/rules/logic/noStaticNodeCondition.ts +43 -0
  39. package/src/rules/logic/noUnnecessaryConditionFalsy.ts +5 -4
  40. package/src/rules/logic/noUnnecessaryConditionTruthy.test.ts +37 -4
  41. package/src/rules/logic/noUnnecessaryConditionTruthy.ts +10 -8
  42. package/src/rules/noReferenceNodeRule.test.ts +140 -0
  43. package/src/rules/noReferenceNodeRule.ts +34 -0
  44. package/src/rules/style/invalidStyleSyntaxRule.test.ts +106 -0
  45. package/src/rules/style/invalidStyleSyntaxRule.ts +35 -0
  46. package/src/searchProject.ts +25 -1
  47. package/src/types.d.ts +20 -2
  48. package/src/util/contextlessEvaluateFormula.test.ts +190 -0
  49. package/src/util/contextlessEvaluateFormula.ts +110 -0
  50. package/src/util/removeUnused.fix.ts +29 -1
  51. package/dist/memos/getAllCustomPropertiesBySyntax.js +0 -43
  52. package/dist/memos/getAllCustomPropertiesBySyntax.js.map +0 -1
  53. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js +0 -50
  54. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.js.map +0 -1
  55. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js +0 -265
  56. package/dist/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.js.map +0 -1
  57. package/src/memos/getAllCustomPropertiesBySyntax.ts +0 -68
  58. package/src/rules/style-variables/ambiguousStyleVariableSyntaxRule.test.ts +0 -278
  59. package/src/rules/style-variables/ambiguousStyleVariableSyntaxRule.ts +0 -65
@@ -0,0 +1,140 @@
1
+ import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
2
+ import { describe, expect, test } from 'bun:test'
3
+ import { fixProject } from '../fixProject'
4
+ import { searchProject } from '../searchProject'
5
+ import { noReferenceNodeRule } from './noReferenceNodeRule'
6
+
7
+ describe('find noReferenceNodeRule', () => {
8
+ test('should detect nodes with no references', () => {
9
+ const problems = Array.from(
10
+ searchProject({
11
+ files: {
12
+ formulas: {},
13
+ components: {
14
+ test: {
15
+ name: 'test',
16
+ nodes: {
17
+ root: {
18
+ id: 'root',
19
+ type: 'element',
20
+ tag: 'div',
21
+ children: [],
22
+ attrs: {},
23
+ style: {},
24
+ events: {},
25
+ classes: {},
26
+ },
27
+ '1LisbD0eCjsuccoUwajn1': {
28
+ id: 'XdhwPGsdFNI4s8A0oMwre',
29
+ type: 'text',
30
+ value: { type: 'value', value: 'Clone the project' },
31
+ },
32
+ },
33
+ formulas: {},
34
+ apis: {},
35
+ attributes: {},
36
+ variables: {},
37
+ },
38
+ },
39
+ },
40
+ rules: [noReferenceNodeRule],
41
+ }),
42
+ )
43
+
44
+ expect(problems).toHaveLength(1)
45
+ expect(problems[0].details).toEqual({ node: '1LisbD0eCjsuccoUwajn1' })
46
+ expect(problems[0].path).toEqual([
47
+ 'components',
48
+ 'test',
49
+ 'nodes',
50
+ '1LisbD0eCjsuccoUwajn1',
51
+ ])
52
+ })
53
+
54
+ test('should not detect nodes with references', () => {
55
+ const problems = Array.from(
56
+ searchProject({
57
+ files: {
58
+ formulas: {},
59
+ components: {
60
+ test: {
61
+ name: 'test',
62
+ nodes: {
63
+ root: {
64
+ id: 'root',
65
+ type: 'element',
66
+ tag: 'div',
67
+ children: ['1LisbD0eCjsuccoUwajn1'],
68
+ attrs: {},
69
+ style: {},
70
+ events: {},
71
+ classes: {},
72
+ },
73
+ '1LisbD0eCjsuccoUwajn1': {
74
+ id: 'XdhwPGsdFNI4s8A0oMwre',
75
+ type: 'text',
76
+ value: { type: 'value', value: 'Clone the project' },
77
+ },
78
+ },
79
+ formulas: {},
80
+ apis: {},
81
+ attributes: {},
82
+ variables: {},
83
+ },
84
+ },
85
+ },
86
+ rules: [noReferenceNodeRule],
87
+ }),
88
+ )
89
+
90
+ expect(problems).toEqual([])
91
+ })
92
+ })
93
+
94
+ describe('fix noReferenceNodeRule', () => {
95
+ test('should delete nodes with no references', () => {
96
+ const files: ProjectFiles = {
97
+ formulas: {},
98
+ components: {
99
+ test: {
100
+ name: 'test',
101
+ nodes: {
102
+ root: {
103
+ id: 'root',
104
+ type: 'element',
105
+ tag: 'div',
106
+ children: ['used'],
107
+ attrs: {},
108
+ style: {},
109
+ events: {},
110
+ classes: {},
111
+ },
112
+ '1LisbD0eCjsuccoUwajn1': {
113
+ id: 'XdhwPGsdFNI4s8A0oMwre',
114
+ type: 'text',
115
+ value: { type: 'value', value: 'Clone the project' },
116
+ },
117
+ used: {
118
+ id: 'used',
119
+ type: 'text',
120
+ value: { type: 'value', value: 'I am used' },
121
+ },
122
+ },
123
+ formulas: {},
124
+ apis: {},
125
+ attributes: {},
126
+ variables: {},
127
+ },
128
+ },
129
+ }
130
+ const fixedFiles = fixProject({
131
+ files,
132
+ rule: noReferenceNodeRule,
133
+ fixType: 'delete-orphan-node',
134
+ })
135
+ expect(Object.keys(fixedFiles.components.test!.nodes)).toEqual([
136
+ 'root',
137
+ 'used',
138
+ ])
139
+ })
140
+ })
@@ -0,0 +1,34 @@
1
+ import type { Rule } from '../types'
2
+ import { removeFromPathFix } from '../util/removeUnused.fix'
3
+
4
+ export const noReferenceNodeRule: Rule<{ node: string }> = {
5
+ code: 'no-reference node',
6
+ level: 'warning',
7
+ category: 'No References',
8
+ visit: (report, args) => {
9
+ if (args.nodeType !== 'component-node') {
10
+ return
11
+ }
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
+ ),
23
+ )
24
+
25
+ if (nodeId !== 'root' && !referencedNodesInComponent.has(nodeId)) {
26
+ report(path, { node: nodeId }, ['delete-orphan-node'])
27
+ }
28
+ },
29
+ fixes: {
30
+ 'delete-orphan-node': removeFromPathFix,
31
+ },
32
+ }
33
+
34
+ export type NoReferenceNodeRuleFix = 'delete-orphan-node'
@@ -0,0 +1,106 @@
1
+ import type { ElementNodeModel } from '@nordcraft/core/dist/component/component.types'
2
+ import type { ProjectFiles } from '@nordcraft/ssr/dist/ssr.types'
3
+ import { describe, expect, test } from 'bun:test'
4
+ import { fixProject } from '../../fixProject'
5
+ import { searchProject } from '../../searchProject'
6
+ import { invalidStyleSyntaxRule } from './invalidStyleSyntaxRule'
7
+
8
+ describe('find invalidStyleSyntaxRule', () => {
9
+ test('should find invalid style syntax', () => {
10
+ const problems = Array.from(
11
+ searchProject({
12
+ files: {
13
+ formulas: {},
14
+ components: {
15
+ test: {
16
+ name: 'test',
17
+ nodes: {
18
+ root: {
19
+ tag: 'ul',
20
+ type: 'element',
21
+ attrs: {},
22
+ style: {
23
+ gap: '8px',
24
+ width: '100%',
25
+ 'max-width': 'calc(NOT VALID',
26
+ height: '/* 100px */ 22px',
27
+ '{': '100px',
28
+ 'border-width': '--my-var',
29
+ },
30
+ events: {},
31
+ classes: {},
32
+ children: [],
33
+ },
34
+ },
35
+ formulas: {},
36
+ apis: {},
37
+ attributes: {},
38
+ variables: {},
39
+ },
40
+ },
41
+ },
42
+ rules: [invalidStyleSyntaxRule],
43
+ }),
44
+ )
45
+
46
+ expect(problems).toHaveLength(2)
47
+ expect(problems[0].code).toBe('invalid style syntax')
48
+ expect(problems[0].path).toEqual([
49
+ 'components',
50
+ 'test',
51
+ 'nodes',
52
+ 'root',
53
+ 'style',
54
+ 'max-width',
55
+ ])
56
+ expect(problems[0].details.property).toBe('max-width')
57
+ expect(problems[1].details.property).toBe('{')
58
+ })
59
+ })
60
+
61
+ describe('fix invalidStyleSyntaxRule', () => {
62
+ test('should remove an invalid style property', () => {
63
+ const files: ProjectFiles = {
64
+ formulas: {},
65
+ components: {
66
+ test: {
67
+ name: 'test',
68
+ nodes: {
69
+ root: {
70
+ tag: 'ul',
71
+ type: 'element',
72
+ attrs: {},
73
+ style: {
74
+ gap: '8px',
75
+ width: '100%',
76
+ 'max-width': 'calc(NOT VALID',
77
+ height: '/* 100px */ 22px',
78
+ '{': '100px',
79
+ },
80
+ events: {},
81
+ classes: {},
82
+ children: [],
83
+ },
84
+ },
85
+ formulas: {},
86
+ apis: {},
87
+ attributes: {},
88
+ variables: {},
89
+ },
90
+ },
91
+ }
92
+ const fixedFiles = fixProject({
93
+ files,
94
+ rule: invalidStyleSyntaxRule,
95
+ fixType: 'delete-style-property',
96
+ })
97
+ expect((fixedFiles.components.test!.nodes.root as ElementNodeModel).style)
98
+ .toMatchInlineSnapshot(`
99
+ {
100
+ "gap": "8px",
101
+ "height": "/* 100px */ 22px",
102
+ "width": "100%",
103
+ }
104
+ `)
105
+ })
106
+ })
@@ -0,0 +1,35 @@
1
+ import { parse } from 'postcss'
2
+ import type { Rule } from '../../types'
3
+ import { removeFromPathFix } from '../../util/removeUnused.fix'
4
+
5
+ export const invalidStyleSyntaxRule: Rule<{
6
+ property: string
7
+ }> = {
8
+ code: 'invalid style syntax',
9
+ level: 'error',
10
+ category: 'Quality',
11
+ visit: (report, { nodeType, value, path, memo }) => {
12
+ if (nodeType !== 'style-declaration') {
13
+ return
14
+ }
15
+ const valid = memo(
16
+ `valid-style-${value.styleProperty}:${value.styleValue}`,
17
+ () => {
18
+ try {
19
+ parse(`${value.styleProperty}: ${value.styleValue}`)
20
+ return true
21
+ } catch {
22
+ return false
23
+ }
24
+ },
25
+ )
26
+ if (!valid) {
27
+ report(path, { property: value.styleProperty }, ['delete-style-property'])
28
+ }
29
+ },
30
+ fixes: {
31
+ 'delete-style-property': removeFromPathFix,
32
+ },
33
+ }
34
+
35
+ export type InvalidStyleSyntaxRuleFix = 'delete-style-property'
@@ -262,7 +262,12 @@ function* visitNode({
262
262
  // The rule must have an implementation for the fix
263
263
  rule.fixes?.[fixOptions.fixType]
264
264
  ) {
265
- const ruleFixes = rule.fixes[fixOptions.fixType]?.(data, state)
265
+ const ruleFixes = rule.fixes[fixOptions.fixType]?.(
266
+ // We must use the path from the report, not the original path
267
+ // because the report might be for a subpath
268
+ { ...data, path },
269
+ state,
270
+ )
266
271
  if (ruleFixes) {
267
272
  fixedFiles = ruleFixes
268
273
  }
@@ -571,6 +576,25 @@ function* visitNode({
571
576
  })
572
577
  }
573
578
  }
579
+ for (const [styleKey, styleValue] of Object.entries(
580
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
581
+ value.style ?? {},
582
+ )) {
583
+ yield* visitNode({
584
+ args: {
585
+ nodeType: 'style-declaration',
586
+ value: { styleProperty: styleKey, styleValue, element: value },
587
+ path: [...path, 'style', styleKey],
588
+ rules,
589
+ files,
590
+ pathsToVisit,
591
+ useExactPaths,
592
+ memo,
593
+ },
594
+ state,
595
+ fixOptions: fixOptions as any,
596
+ })
597
+ }
574
598
  }
575
599
  break
576
600
 
package/src/types.d.ts CHANGED
@@ -14,9 +14,9 @@ import type { ToddleComponent } from '@nordcraft/core/dist/component/ToddleCompo
14
14
  import type { Formula } from '@nordcraft/core/dist/formula/formula'
15
15
  import type { PluginFormula } from '@nordcraft/core/dist/formula/formulaTypes'
16
16
  import type { Theme } from '@nordcraft/core/dist/styling/theme'
17
+ import type { PluginAction } from '@nordcraft/core/dist/types'
17
18
  import type {
18
19
  ApiService,
19
- PluginAction,
20
20
  ProjectFiles,
21
21
  Route,
22
22
  ToddleProject,
@@ -30,9 +30,11 @@ import type { NoReferenceEventRuleFix } from './rules/events/noReferenceEventRul
30
30
  import type { LegacyFormulaRuleFix } from './rules/formulas/legacyFormulaRule'
31
31
  import type { NoReferenceComponentFormulaRuleFix } from './rules/formulas/noReferenceComponentFormulaRule'
32
32
  import type { NoReferenceProjectFormulaRuleFix } from './rules/formulas/noReferenceProjectFormulaRule'
33
+ import type { NoStaticNodeConditionRuleFix } from './rules/logic/noStaticNodeConditionRule'
34
+ import type { NoReferenceNodeRuleFix } from './rules/noReferenceNodeRule'
35
+ import type { InvalidStyleSyntaxRuleFix } from './rules/style/invalidStyleSyntaxRule'
33
36
 
34
37
  type Code =
35
- | 'ambiguous style variable syntax'
36
38
  | 'duplicate event trigger'
37
39
  | 'duplicate url parameter'
38
40
  | 'duplicate workflow parameter'
@@ -40,6 +42,7 @@ type Code =
40
42
  | 'invalid api parser mode'
41
43
  | 'invalid api proxy body setting'
42
44
  | 'invalid element child'
45
+ | 'invalid style syntax'
43
46
  | 'legacy action'
44
47
  | 'legacy api'
45
48
  | 'legacy formula'
@@ -53,9 +56,11 @@ type Code =
53
56
  | 'no-reference component workflow'
54
57
  | 'no-reference component'
55
58
  | 'no-reference event'
59
+ | 'no-reference node'
56
60
  | 'no-reference project action'
57
61
  | 'no-reference project formula'
58
62
  | 'no-reference variable'
63
+ | 'no-static-node-condition'
59
64
  | 'no-unnecessary-condition-falsy'
60
65
  | 'no-unnecessary-condition-truthy'
61
66
  | 'non-empty void element'
@@ -297,6 +302,15 @@ type StyleVariantNode = {
297
302
  value: { variant: StyleVariant; element: ElementNodeModel }
298
303
  } & Base
299
304
 
305
+ type StyleNode = {
306
+ nodeType: 'style-declaration'
307
+ value: {
308
+ styleProperty: string
309
+ styleValue: string
310
+ element: ElementNodeModel
311
+ }
312
+ } & Base
313
+
300
314
  export type NodeType =
301
315
  | ActionModelNode
302
316
  | ComponentAPIInputNode
@@ -316,6 +330,7 @@ export type NodeType =
316
330
  | ProjectFormulaNode
317
331
  | ProjectThemeNode
318
332
  | ProjectRoute
333
+ | StyleNode
319
334
  | StyleVariantNode
320
335
 
321
336
  type FixType =
@@ -328,6 +343,9 @@ type FixType =
328
343
  | NoReferenceAttributeRuleFix
329
344
  | NoReferenceEventRuleFix
330
345
  | NoReferenceComponentFormulaRuleFix
346
+ | NoReferenceNodeRuleFix
347
+ | InvalidStyleSyntaxRuleFix
348
+ | NoStaticNodeConditionRuleFix
331
349
 
332
350
  export interface Rule<T = unknown, V = NodeType> {
333
351
  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
+ })