Repository: jquense/yup
Pull request #1757 (2022-08-22 18:56:28)

feat: respect nullable() with oneOf
closes: #768 #104

BREAKING CHANGE: previously `oneOf` required adding `null` explicitly to allowed values when using oneOf. Folks have found this confusing and unintuitive so I am deferring and adjusting the behavior


Patch:
diff --git a/src/schema.ts b/src/schema.ts
index d2a0b26..a3c94ac 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -848,16 +848,16 @@ export default abstract class Schema<
     next.internalTests.whiteList = createValidation({
       message,
       name: 'oneOf',
+      skipAbsent: true,
       test(value) {
-        if (value === undefined) return true;
-        let valids = this.schema._whitelist;
+        let valids = (this.schema as Schema)._whitelist;
         let resolved = valids.resolveAll(this.resolve);
 
         return resolved.includes(value)
           ? true
           : this.createError({
               params: {
-                values: valids.toArray().join(', '),
+                values: Array.from(valids).join(', '),
                 resolved,
               },
             });
@@ -881,12 +881,12 @@ export default abstract class Schema<
       message,
       name: 'notOneOf',
       test(value) {
-        let invalids = this.schema._blacklist;
+        let invalids = (this.schema as Schema)._blacklist;
         let resolved = invalids.resolveAll(this.resolve);
         if (resolved.includes(value))
           return this.createError({
             params: {
-              values: invalids.toArray().join(', '),
+              values: Array.from(invalids).join(', '),
               resolved,
             },
           });
diff --git a/src/util/ReferenceSet.ts b/src/util/ReferenceSet.ts
index f9974af..6388c08 100644
--- a/src/util/ReferenceSet.ts
+++ b/src/util/ReferenceSet.ts
@@ -1,59 +1,33 @@
 import type { SchemaRefDescription } from '../schema';
 import Reference from '../Reference';
 
-export default class ReferenceSet {
-  list: Set<unknown>;
-  refs: Map<string, Reference>;
-
-  constructor() {
-    this.list = new Set();
-    this.refs = new Map();
-  }
-  get size() {
-    return this.list.size + this.refs.size;
-  }
-
+export default class ReferenceSet extends Set<unknown | Reference> {
   describe() {
     const description = [] as Array<unknown | SchemaRefDescription>;
 
-    for (const item of this.list) description.push(item);
-    for (const [, ref] of this.refs) description.push(ref.describe());
-
+    for (const item of this.values()) {
+      description.push(Reference.isRef(item) ? item.describe() : item);
+    }
     return description;
   }
 
-  toArray() {
-    return Array.from(this.list).concat(Array.from(this.refs.values()));
-  }
-
-  resolveAll(resolve: (v: unknown) => unknown) {
-    return this.toArray().reduce((acc: unknown[],e) => acc.concat(Reference.isRef(e) ? resolve(e) : e),[]);
-  }
-
-  add(value: unknown) {
-    Reference.isRef(value)
-      ? this.refs.set(value.key, value)
-      : this.list.add(value);
-  }
-  delete(value: unknown) {
-    Reference.isRef(value)
-      ? this.refs.delete(value.key)
-      : this.list.delete(value);
+  resolveAll(resolve: (v: unknown | Reference) => unknown) {
+    let result = [] as unknown[];
+    for (const item of this.values()) {
+      result.push(resolve(item));
+    }
+    return result;
   }
 
   clone() {
-    const next = new ReferenceSet();
-    next.list = new Set(this.list);
-    next.refs = new Map(this.refs);
-    return next;
+    return new ReferenceSet(this.values());
   }
 
   merge(newItems: ReferenceSet, removeItems: ReferenceSet) {
     const next = this.clone();
-    newItems.list.forEach((value) => next.add(value));
-    newItems.refs.forEach((value) => next.add(value));
-    removeItems.list.forEach((value) => next.delete(value));
-    removeItems.refs.forEach((value) => next.delete(value));
+
+    newItems.forEach((value) => next.add(value));
+    removeItems.forEach((value) => next.delete(value));
     return next;
   }
 }


Test patch:
diff --git a/test/mixed.ts b/test/mixed.ts
index 1521c79..8d7cc1c 100644
--- a/test/mixed.ts
+++ b/test/mixed.ts
@@ -255,11 +255,11 @@ describe('Mixed Types ', () => {
     let inst = mixed().oneOf(['hello']);
 
     validateAll(inst, {
-      valid: [undefined, 'hello'],
+      valid: [undefined, 'hello', [null, inst.nullable()]],
       invalid: [
         'YOLO',
         [undefined, inst.required(), 'required'],
-        [null, inst.nullable()],
+        // [null, inst.nullable()],
         [null, inst.nullable().required(), 'required'],
       ],
     });
diff --git a/test/types/types.ts b/test/types/types.ts
index 6cfedb4..e147c23 100644
--- a/test/types/types.ts
+++ b/test/types/types.ts
@@ -964,12 +964,11 @@ Conditions: {
 }
 
 TypeAssigning: {
-  // $ExpectError unknown is not assignable to () => any
   const _schema: ObjectSchema<{
     mtime?: Date | null | undefined;
     toJSON: () => any;
   }> = object({
     mtime: date().nullable(),
-    toJSON: mixed().required(),
+    toJSON: mixed<() => any>().required(),
   });
 }