Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions frontend/common/utils/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// These stores pull in `window` globals (via the flux dispatcher) that
// aren't available under the jest `node` test environment, so they're
// mocked out to allow `common/utils/utils` to load for real.
jest.mock('common/stores/account-store', () => ({}))
jest.mock('common/stores/project-store', () => ({}))
jest.mock('common/store', () => ({ getStore: () => ({}) }))
jest.mock('@flagsmith/flagsmith', () => ({
getValue: (_key: string, opts: { fallback: unknown }) => opts.fallback,
}))

import Utils from 'common/utils/utils'
import { SegmentCondition } from 'common/types/responses'

const buildRule = (overrides: Partial<SegmentCondition>): SegmentCondition => ({
operator: 'EQUAL',
property: 'trait',
value: '',
...overrides,
})

describe('validateRule', () => {
it('returns false for a falsy rule', () => {
expect(Utils.validateRule(undefined as unknown as SegmentCondition)).toBe(
false,
)
})

it('returns true for a rule marked as deleted regardless of its value', () => {
const rule = buildRule({
delete: true,
operator: 'MODULO',
value: undefined as unknown as string,
})
expect(() => Utils.validateRule(rule)).not.toThrow()
expect(Utils.validateRule(rule)).toBe(true)
})

describe('does not throw and returns false when value is missing', () => {
it.each<[string, SegmentCondition['value']]>([
['undefined', undefined as unknown as string],
['null', null],
['empty string', ''],
])('MODULO with %s value', (_label, value) => {
const rule = buildRule({ operator: 'MODULO', value })
expect(() => Utils.validateRule(rule)).not.toThrow()
expect(Utils.validateRule(rule)).toBe(false)
})

it.each<[string, SegmentCondition['value']]>([
['undefined', undefined as unknown as string],
['null', null],
['empty string', ''],
])('semver operator with %s value', (_label, value) => {
const rule = buildRule({ operator: 'GREATER_THAN:semver', value })
expect(() => Utils.validateRule(rule)).not.toThrow()
expect(Utils.validateRule(rule)).toBe(false)
})
})

it('does not throw when value is a non-string type for MODULO', () => {
const rule = buildRule({ operator: 'MODULO', value: true })
expect(() => Utils.validateRule(rule)).not.toThrow()
})

it('does not throw when value is a non-string type for a semver operator', () => {
const rule = buildRule({ operator: 'GREATER_THAN:semver', value: true })
expect(() => Utils.validateRule(rule)).not.toThrow()
})
Comment on lines +60 to +68

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the validation result for non-string values.

These tests only verify that Utils.validateRule does not throw. They do not verify that the supplied true value is rejected. Add toBe(false) assertions for both the MODULO and semver cases. The SegmentCondition.value contract permits booleans, so the validation result is part of this regression contract.

Suggested assertions
     expect(() => Utils.validateRule(rule)).not.toThrow()
+    expect(Utils.validateRule(rule)).toBe(false)

Apply the same assertion to both tests.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('does not throw when value is a non-string type for MODULO', () => {
const rule = buildRule({ operator: 'MODULO', value: true })
expect(() => Utils.validateRule(rule)).not.toThrow()
})
it('does not throw when value is a non-string type for a semver operator', () => {
const rule = buildRule({ operator: 'GREATER_THAN:semver', value: true })
expect(() => Utils.validateRule(rule)).not.toThrow()
})
it('does not throw when value is a non-string type for MODULO', () => {
const rule = buildRule({ operator: 'MODULO', value: true })
expect(() => Utils.validateRule(rule)).not.toThrow()
expect(Utils.validateRule(rule)).toBe(false)
})
it('does not throw when value is a non-string type for a semver operator', () => {
const rule = buildRule({ operator: 'GREATER_THAN:semver', value: true })
expect(() => Utils.validateRule(rule)).not.toThrow()
expect(Utils.validateRule(rule)).toBe(false)
})


it('is valid for a hideValue operator (e.g. IS_SET) even with a null value', () => {
const rule = buildRule({ operator: 'IS_SET', value: null })
expect(() => Utils.validateRule(rule)).not.toThrow()
expect(Utils.validateRule(rule)).toBe(true)
})

it('validates a correct MODULO value', () => {
const rule = buildRule({ operator: 'MODULO', value: '2|1' })
expect(Utils.validateRule(rule)).toBe(true)
})

it('validates a correct semver value', () => {
const rule = buildRule({
operator: 'GREATER_THAN:semver',
value: '1.0.0:semver',
})
expect(Utils.validateRule(rule)).toBe(true)
})

it('rejects an invalid semver value', () => {
const rule = buildRule({
operator: 'GREATER_THAN:semver',
value: 'not-a-version',
})
expect(Utils.validateRule(rule)).toBe(false)
})
})
4 changes: 2 additions & 2 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ const Utils = Object.assign({}, BaseUtils, {
if (!rule.value) {
return false
}
return !!semver.valid(`${rule.value.split(':')[0]}`)
return !!semver.valid(`${rule.value}`.split(':')[0])
}

switch (rule.operator) {
Expand All @@ -846,7 +846,7 @@ const Utils = Object.assign({}, BaseUtils, {
if (!rule.value) {
return false
}
const valueSplit = rule.value.split('|')
const valueSplit = `${rule.value}`.split('|')
if (valueSplit.length === 2) {
const [divisor, remainder] = [
parseFloat(valueSplit[0]),
Expand Down
Loading