diff --git a/NOTICE-js b/NOTICE-js index c111a2e1..cb1e7f8c 100644 --- a/NOTICE-js +++ b/NOTICE-js @@ -2706,6 +2706,8 @@ Copyright (c) Microsoft Corporation. Copyright (c) Microsoft Corporation. ** @types/yargs-parser; version 15.0.0 -- Copyright (c) Microsoft Corporation. +** @types/yup; version 0.29.8 -- +Copyright (c) Microsoft Corporation. ** @typescript-eslint/eslint-plugin; version 4.3.0 -- https://github.com/typescript-eslint/typescript-eslint#readme Copyright (c) 2019 TypeScript ESLint and other contributors ** @typescript-eslint/experimental-utils; version 4.3.0 -- https://github.com/typescript-eslint/typescript-eslint#readme @@ -3087,9 +3089,6 @@ Copyright (c) 2014 Sebastien Balayn Copyright (c) 2016 Michael Pratt Copyright (c) 2018 Michael Pratt Copyright (c) 2015 Alexandre Kirszenberg -** ccount; version 1.0.5 -- https://github.com/wooorm/ccount#readme -(c) Titus Wormer -Copyright (c) 2015 Titus Wormer MIT License @@ -3129,8 +3128,6 @@ Copyright (c) 2012, 2011 Ariya Hidayat (http://ariya.ofilabs.com/about) (twitter ** ejs; version 3.1.3 -- https://github.com/mde/ejs Copyright Joyent, Inc. and other Node contributors. ** eslint-visitor-keys; version 1.3.0 -- https://github.com/eslint/eslint-visitor-keys#readme -** fast-diff; version 1.1.2 -- https://github.com/jhchen/fast-diff#readme -Copyright 2006 Google Inc. http://code.google.com/p/google-diff-match-patch Apache License @@ -3417,6 +3414,9 @@ THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO ------ +** ccount; version 1.0.5 -- https://github.com/wooorm/ccount#readme +(c) Titus Wormer +Copyright (c) 2015 Titus Wormer ** chalk; version 4.1.0 -- https://github.com/chalk/chalk#readme Copyright (c) Sindre Sorhus (sindresorhus.com) ** char-regex; version 1.0.2 -- @@ -4023,6 +4023,8 @@ For the general privacy policy governing access to this site, see the Unicode Pr 4. Severability. If any provision of this Agreement is declared invalid or unenforceable, the remaining provisions of this Agreement shall remain in effect. 5. Entire Agreement. This Agreement constitutes the entire agreement between the parties. +** fast-diff; version 1.1.2 -- https://github.com/jhchen/fast-diff#readme +Copyright 2006 Google Inc. http://code.google.com/p/google-diff-match-patch ** fb-watchman; version 2.0.1 -- https://facebook.github.io/watchman/ Copyright 2014-present Facebook, Inc. ** filelist; version 1.0.1 -- https://github.com/mde/filelist @@ -4214,11 +4216,6 @@ Copyright 2012-2015, Yahoo Inc. Copyright 2015 Yahoo! Inc. Copyright 2015, Yahoo Inc. Copyright 2012-2015, Yahoo Inc. -** istanbul-reports; version 3.0.2 -- https://istanbul.js.org/ -(c) Sindre Sorhus -Copyright 2012-2015 Yahoo! Inc. -Copyright 2012-2015, Yahoo Inc. -Copyright (c) Facebook, Inc. and its affiliates. Copyright (c) . All rights reserved. @@ -4950,6 +4947,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ------ +** istanbul-reports; version 3.0.2 -- https://istanbul.js.org/ +(c) Sindre Sorhus +Copyright 2012-2015 Yahoo! Inc. +Copyright 2012-2015, Yahoo Inc. +Copyright (c) Facebook, Inc. and its affiliates. ** js-base64; version 2.5.2 -- https://github.com/dankogai/js-base64#readme Copyright (c) 2014, Dan Kogai ** lolex; version 2.7.5 -- http://github.com/sinonjs/lolex @@ -5692,9 +5694,6 @@ Copyright (c) Sindre Sorhus (sindresorhus.com) ** os-locale; version 3.1.0 -- https://github.com/sindresorhus/os-locale#readme (c) Sindre Sorhus (https://sindresorhus.com) Copyright (c) Sindre Sorhus (sindresorhus.com) -** os-tmpdir; version 1.0.2 -- https://github.com/sindresorhus/os-tmpdir#readme -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) MIT License @@ -6039,7 +6038,6 @@ Copyright (c) Isaac Z. Schlueter and Contributors Copyright (c) 2018, Aleck Greenham Copyright (c) Facebook, Inc. and its affiliates. ** remove-trailing-separator; version 1.1.0 -- https://github.com/darsain/remove-trailing-separator#readme -** request-promise-core; version 1.1.4 -- https://github.com/request/promise-core#readme ISC License @@ -6053,6 +6051,9 @@ THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO ------ +** os-tmpdir; version 1.0.2 -- https://github.com/sindresorhus/os-tmpdir#readme +(c) Sindre Sorhus (https://sindresorhus.com) +Copyright (c) Sindre Sorhus (sindresorhus.com) ** overlayscrollbars; version 1.13.0 -- https://kingsora.github.io/OverlayScrollbars Copyright (c) 2017 Rene Haas Copyright (c) 2001 Robert Penner @@ -6696,7 +6697,6 @@ Copyright (c) 1991-2017 Unicode, Inc. Copyright (c) 2018 The Khronos Group Inc. (c) Microsoft Corporation. Zhu Zuo Quan Suo Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS -** validate-npm-package-license; version 3.0.4 -- https://github.com/kemitchell/validate-npm-package-license.js#readme Apache License @@ -7012,6 +7012,7 @@ For these and/or other purposes and motivations, and without any expectation of ------ +** request-promise-core; version 1.1.4 -- https://github.com/request/promise-core#readme ** request-promise-native; version 1.0.9 -- https://github.com/request/request-promise-native#readme ** require-main-filename; version 2.0.0 -- https://github.com/yargs/require-main-filename#readme Copyright (c) 2016 @@ -7649,6 +7650,7 @@ Copyright (c) Sindre Sorhus (sindresorhus.com) MIT OR (CC0-1.0 AND MIT) ** lodash-joins; version 3.0.1 -- https://github.com/mtraynham/lodash-joins#readme Copyright (c) Microsoft Corporation. +** validate-npm-package-license; version 3.0.4 -- https://github.com/kemitchell/validate-npm-package-license.js#readme ** walker; version 1.0.7 -- https://github.com/daaku/nodejs-walker Copyright 2013 Naitik Shah ** websocket-driver; version 0.7.4 -- https://github.com/faye/websocket-driver-node diff --git a/app/javascript/common/yup/index.ts b/app/javascript/common/yup/index.ts new file mode 100644 index 00000000..cbe5ab5f --- /dev/null +++ b/app/javascript/common/yup/index.ts @@ -0,0 +1,13 @@ +export * from 'yup'; +export * from './yup'; + +/** + * NEVER CALL THIS FUNCTION FROM YOUR CODE. IT WILL THROW AN EXCEPTION + * + * setLocale is handled in `app/javascripts/common/yup/yup.ts` + * @throws Error + */ +// eslint-disable-next-line @typescript-eslint/no-empty-function +export function setLocale():never { + throw new Error('setLocale is handled in `app/javascripts/common/yup/yup.ts`. NEVER call this function from your code'); +} diff --git a/app/javascript/common/yup/yup.spec.ts b/app/javascript/common/yup/yup.spec.ts new file mode 100644 index 00000000..9b24cd30 --- /dev/null +++ b/app/javascript/common/yup/yup.spec.ts @@ -0,0 +1,45 @@ +// License: LGPL-3.0-or-later +// yup errors are super confusing until formik turns them into something logical +import { validateYupSchema, yupToFormErrors } from 'formik'; + +import * as yup from './'; +import {createMessage, YupFail} from './'; + + +describe("yup", () => { + describe('.createMessage', () => { + const nameLabel = 'Name'; + const customTranslationId = 'translation.id.path'; + it('createMessage creates the proper functions', async() => { + expect.assertions(4); + + const schema = yup.object({ + name: yup.string().label(nameLabel).min(20), + id: yup.string().required(createMessage(({ path}) => (new YupFail( customTranslationId, {path})))), + address: yup.object({ + city: yup.string().required(), + state: yup.string(), + }), + }); + + + // This is the equivalent of getting errors from the FormikContext + let errors:any = null; + try { + await validateYupSchema({name: "not 20 chars"}, schema, false); + } + catch(e) { + //turn into useful errors + errors = yupToFormErrors(e); + } + + expect(errors.name.message).toStrictEqual([{id:"yup.string.min"}, {path: nameLabel, min: 20}]); + expect(errors.id.message).toStrictEqual([{id:customTranslationId}, {path: 'id'}]); + expect(errors.address.city.message).toStrictEqual([{id:'yup.mixed.required'}, {path: 'address.city'}]); + expect(errors.address.state).toBeUndefined(); + + }); + + }); + +}); diff --git a/app/javascript/common/yup/yup.ts b/app/javascript/common/yup/yup.ts new file mode 100644 index 00000000..9c5466e3 --- /dev/null +++ b/app/javascript/common/yup/yup.ts @@ -0,0 +1,148 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { setLocale, LocaleObject, TestMessageParams } from 'yup'; +import type {IntlShape} from '../../components/intl'; + + +/** + * Wraps a validation failure from yup + * @date 2020-10-24 + * @export + * @class YupFail + * @example + * const schema = yup.schema({ + * // we use createMessage to make sure we provide the correct function to min + * name: yup.string.min(20, createMessage( + * //we're taking the path and min properties of the min function's result + * ({path, label, min}) => { + * if (label) { + * // in yup messages, path represents the name of the field. + * // if label is provided, that's a better name + * path = label; + * } + * return new YupFail('translation.id.path', + * {path, min} // you HAVE to pass path here, even if the message doesn't use it + * ); + * } + * )) + * }) + * + * // the yup schema was passed into Formik and formikConfig.errors has been set for the name + * // field. We assume the `errors` const has latest formikConfig.errors + * + * // the below code is part of a TSX component + *
    + * { + * errors.name.map((yupFail:YupFail) => ( + *
  • {formatMessage(...yupFail.message)
  • + * ) + * ) + * } + *
+ * + */ +export class YupFail { + + /** + * Creates an instance of Description. + * @date 2020-10-24 + * @param id the translation ID of the failure message + * @param values the values to be interpolated into the failure message ID'd by {@link id} + */ + constructor(readonly id: string, readonly values: Parameters[0]) { + this.id = id; + this.values = values; + Object.bind(this, this.message); + } + + get message(): Parameters { + return [{id:this.id}, this.values]; + } + +} +type RequiredValueProps = {path:string}; +/** + * A spec + */ +type TestOptionsMessageFn = Record, R = any> = + | ((params: Extra & Partial & RequiredValueProps) => R) + + + +export type ToPropResult = Record> = TestOptionsMessageFn + + +/** + * A function used for improving typescript safety when manually creating your + * own validation messages + * @param fn a function + */ +export function createMessage = Record>(fn: ToPropResult): ToPropResult { + return fn; +} + + + +// } + +type HoudiniYupLocaleObject = Required; + +// TODO: we could optimize this and only run it the first time the module is +// loaded + +const locale: HoudiniYupLocaleObject = { + mixed: { + default: message("yup.mixed.default"), + required: message("yup.mixed.required"), + oneOf: message("yup.mixed.oneOf"), + notOneOf: message("yup.mixed.notOneOf"), + }, + string: { + length: message("yup.string.length"), + min: message("yup.string.min"), + max: message("yup.string.max"), + matches: message("yup.string.regex"), + email: message("yup.string.email"), + url: message("yup.string.url"), + uuid: message("yup.string.uuid"), + trim: message("yup.string.trim"), + lowercase: message("yup.string.lowercase"), + uppercase: message("yup.string.uppercase"), + }, + number: { + min: message("yup.number.min"), + max: message("yup.string.max"), + lessThan: message("yup.number.lessThan"), + moreThan: message("yup.number.moreThan"), + + positive: message("yup.number.postive"), + negative: message("yup.number.negative"), + integer: message("yup.number.integer"), + }, + date: { + min: message("yup.date.min"), + max: message("yup.data.max"), + }, + + boolean: { + + }, + object: { + noUnknown: message("yup.object.noUnknown"), + }, + + array: { + min: message("yup.array.min"), + max: message("yup.array.max"), + }, +}; + +function message = Record>(id: string): ToPropResult { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + return createMessage(({ path, label, value, originalValue, ...other }) => { + if (label) { + path = label; + } + return new YupFail(id, { path, ...other }); + }); +} +setLocale(locale);