Skip to content

Commit

Permalink
fix: fixed autoDirty and rewardEarly behaviours
Browse files Browse the repository at this point in the history
  • Loading branch information
victorgarciaesgi committed Dec 8, 2024
1 parent df235bd commit bbc8de4
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 44 deletions.
12 changes: 7 additions & 5 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ about: Create a report to help me improve
title: ''
labels: ''
assignees: ''

---

**Environnement infos**

`@regle/x` version:
`vue` version:
`typecript` versinon:
- `@regle/x` version:
- `vue` version:
- `typescript` version:
- `Nuxt` version (if you use Nuxt):
- `Zod` version (if you use zod):

**Describe the bug**
A clear and concise description of what the bug is.
Expand All @@ -22,5 +24,5 @@ A clear and concise description of what you expected to happen.
Please provide a reproduction link or repo

**Screenshots**
If applicable, add screenshots to help explain your problem.
If applicable, add screenshots or recordings to help explain your problem.

3 changes: 2 additions & 1 deletion docs/src/core-concepts/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ Type: `boolean`

Default: `false`

Turn on the `reward-early-punish-late` mode of Regle. This mode will not set fields as invalid once they are valid, unless manually triggered by or `$validate` method
Turn on the `reward-early-punish-late` mode of Regle. This mode will not set fields as invalid once they are valid, unless manually triggered by or `$validate` method.

This will have effects only if you use `autoDirty: false`.

### `clearExternalErrorsOnChange`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,10 @@ export function createReactiveCollectionStatus({
collectionScopes = [];
}

function $touch(runCommit = true): void {
$fieldStatus.value.$touch(runCommit);
function $touch(runCommit = true, withConditions = false): void {
$fieldStatus.value.$touch(runCommit, withConditions);
$eachStatus.value.forEach(($each) => {
$each.$touch(runCommit);
$each.$touch(runCommit, withConditions);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ export function createReactiveFieldStatus({
});

const $rewardEarly = computed<boolean | undefined>(() => {
if ($autoDirty.value === true) {
return false;
}
if ($localOptions.value.$rewardEarly != null) {
return $localOptions.value.$rewardEarly;
}
Expand Down Expand Up @@ -344,7 +347,7 @@ export function createReactiveFieldStatus({

$unwatchState = watch(
state,
() => {
(newValue, oldValue) => {
if (scopeState.$autoDirty.value) {
if (!$dirty.value) {
$dirty.value = true;
Expand All @@ -353,7 +356,12 @@ export function createReactiveFieldStatus({
if (rulesDef.value instanceof Function) {
createReactiveRulesResult();
}
$commit();
if (
scopeState.$autoDirty.value ||
(scopeState.$rewardEarly.value && scopeState.$error.value)
) {
$commit();
}
if (
scopeState.$rewardEarly.value !== true &&
scopeState.$clearExternalErrorsOnChange.value
Expand Down Expand Up @@ -401,13 +409,20 @@ export function createReactiveFieldStatus({
}
}

function $touch(runCommit = true): void {
function $touch(runCommit = true, withConditions = false): void {
if (!$dirty.value) {
$dirty.value = true;
}

if (runCommit) {
if (withConditions && runCommit) {
if (
scopeState.$autoDirty.value ||
(scopeState.$rewardEarly.value && scopeState.$error.value)
) {
$commit();
}
} else if (runCommit) {
$commit();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import type { ComputedRef, EffectScope, Ref, ToRefs, WatchStopHandle } from 'vue
import {
computed,
effectScope,
nextTick,
reactive,
ref,
toRaw,
toRef,
triggerRef,
unref,
Expand Down Expand Up @@ -201,9 +199,9 @@ export function createReactiveNestedStatus({
define$WatchExternalErrors();
}

function $touch(runCommit = true): void {
function $touch(runCommit = true, withConditions = false): void {
Object.values($fields.value).forEach((statusOrField) => {
statusOrField.$touch(runCommit);
statusOrField.$touch(runCommit, withConditions);
});
}

Expand Down Expand Up @@ -263,7 +261,7 @@ export function createReactiveNestedStatus({
// Do not watch deep to only track mutation on the object itself on not its children
$unwatch();
createReactiveFieldsStatus();
$touch();
$touch(true, true);
},
{ flush: 'sync' }
);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/types/rules/rule.status.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export interface RegleCommonStatus<TValue = any> {
readonly $name: string;
$id?: string;
$value: UnwrapNestedRefs<TValue>;
$touch(runCommit?: boolean): void;
$touch(runCommit?: boolean, withConditions?: boolean): void;
$reset(): void;
$resetAll: () => void;
$unwatch(): void;
Expand Down
17 changes: 11 additions & 6 deletions tests/fixtures/rules.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { createRule } from '@regle/core';
import { ruleHelpers } from '@regle/rules';
import { timeout } from '../utils';

export const mockedValidations = {
isFoo: vi.fn((value) => value === 'foo'),
isEven: vi.fn((value) => value % 2 === 0),
};

export const ruleMockIsFoo = createRule({
validator(value: Maybe<string>) {
if (ruleHelpers.isFilled(value)) {
return value === 'foo';
return mockedValidations.isFoo(value);
}
return true;
},
Expand All @@ -16,7 +21,7 @@ export const ruleMockIsFoo = createRule({
export const ruleMockIsEven = createRule({
validator(value: Maybe<number>) {
if (ruleHelpers.isFilled(value)) {
return value % 2 === 0;
return mockedValidations.isEven(value);
}
return true;
},
Expand All @@ -28,7 +33,7 @@ export function ruleMockIsEvenAsync(time = 1000) {
async validator(value: Maybe<number>) {
if (ruleHelpers.isFilled(value)) {
await timeout(time);
return value % 2 === 0;
return mockedValidations.isEven(value);
}
return true;
},
Expand All @@ -41,7 +46,7 @@ export function ruleMockIsFooAsync() {
async validator(value: Maybe<string>) {
if (ruleHelpers.isFilled(value)) {
await timeout(1000);
return value === 'foo';
return mockedValidations.isFoo(value);
}
return true;
},
Expand Down Expand Up @@ -76,15 +81,15 @@ export const ruleMockIsEqualParamAsync = createRule({

export const inlineRuleMockIsFoo = (value: any) => {
if (ruleHelpers.isFilled(value)) {
return value === 'foo';
return mockedValidations.isFoo(value);
}
return true;
};

export const inlineRuleAsyncMockIsFoo = async (value: any) => {
if (ruleHelpers.isFilled(value)) {
await timeout(1000);
return value === 'foo';
return mockedValidations.isFoo(value);
}
return true;
};
33 changes: 20 additions & 13 deletions tests/fixtures/validations.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type { RefSymbol } from '@vue/reactivity';

type ReturnRegleType = ReturnType<typeof nestedReactiveObjectValidation>;

export function nestedReactiveObjectValidation() {
export function nestedReactiveObjectValidation(autoDirty = true, rewardEarly = false) {
const form = reactive({
level0: 0,
level1: {
Expand All @@ -19,20 +19,27 @@ export function nestedReactiveObjectValidation() {
collection: [{ name: 0 as number | null }],
},
});
return useRegle(form, {
level0: { rule: ruleMockIsEven },
level1: {
child: { rule: ruleMockIsEven },
level2: {
return useRegle(
form,
{
level0: { rule: ruleMockIsEven },
level1: {
child: { rule: ruleMockIsEven },
},
collection: {
$each: {
name: { required, ruleMockIsEven },
level2: {
child: { rule: ruleMockIsEven },
},
collection: {
$each: {
name: { required, ruleMockIsEven },
},
},
},
},
});
{
autoDirty,
rewardEarly,
}
);
}

export function nestedRefObjectValidation(): ReturnRegleType {
Expand Down Expand Up @@ -172,7 +179,7 @@ export function computedValidationsObjectWithRefs(): any {
return { form: { conditional, number }, ...useRegle({ number, conditional }, validations) };
}

export function simpleNestedStateWithMixedValidation(autoDirty = true) {
export function simpleNestedStateWithMixedValidation(autoDirty = true, rewardEarly = false) {
const form = ref({
email: '',
user: {
Expand All @@ -196,7 +203,7 @@ export function simpleNestedStateWithMixedValidation(autoDirty = true) {
},
},
},
{ autoDirty }
{ autoDirty, rewardEarly }
);
}

Expand Down
26 changes: 26 additions & 0 deletions tests/unit/createRule/createRule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,30 @@ describe('createRule', () => {

expect(await rule.exec('fooo')).toBe(false);
});

it('should recognize mutliple parameters with default', async () => {
const rule = createRule({
validator(value, param = false, param2 = true) {
return true;
},
message: '',
});

expect(rule().exec('fooo')).toBe(true);
expect(rule(true).exec('fooo')).toBe(true);
expect(rule(true, true).exec('fooo')).toBe(true);
});

it('should recognize mutliple parameters with spread', async () => {
const rule = createRule({
validator(value, ...params: any[]) {
return true;
},
message: '',
});

expect(rule().exec('fooo')).toBe(true);
expect(rule(true).exec('fooo')).toBe(true);
expect(rule(true, true).exec('fooo')).toBe(true);
});
});
2 changes: 1 addition & 1 deletion tests/unit/useRegle/global-config/global-rules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ruleMockIsEven } from '../../../fixtures';
import { minValue, required, withMessage } from '@regle/rules';
import { createRegleComponent } from '../../../utils/test.utils';

export function nestedRefObjectValidation() {
function nestedRefObjectValidation() {
const { useRegle } = defineRegleConfig({
rules: () => ({
minValue: withMessage(minValue, (_, { $params: [min] }) => `Patched min:${min}`),
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/useRegle/properties/$autoDirty.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe.each([
describe.each([
['local modifier', () => simpleNestedStateWithMixedValidation(false)],
['global modifier', () => simpleNestedStateWithMixedValidationAndGlobalConfig(false)],
])('$autoDirty -> false', (name, rules) => {
])('$autoDirty -> false - %s', (name, rules) => {
beforeAll(() => {
vi.useFakeTimers();
});
Expand All @@ -67,7 +67,7 @@ describe.each([

vm.r$.$value.email = 'foo';
await nextTick();
shouldBeInvalidField(vm.r$.$fields.email);
shouldBePristineField(vm.r$.$fields.email);

await Promise.all([vm.r$.$validate(), vi.advanceTimersByTimeAsync(200), vm.$nextTick]);

Expand All @@ -86,7 +86,7 @@ describe.each([

vm.r$.$value.email = 'foo';
await nextTick();
shouldBeInvalidField(vm.r$.$fields.email);
shouldBePristineField(vm.r$.$fields.email);

vm.r$.$touch();
await nextTick();
Expand All @@ -106,7 +106,7 @@ describe.each([

vm.r$.$value.email = 'foo';
await nextTick();
shouldBeInvalidField(vm.r$.$fields.email);
shouldBePristineField(vm.r$.$fields.email);

vm.r$.$touch();
await nextTick();
Expand Down
Loading

0 comments on commit bbc8de4

Please sign in to comment.