Creating intl expected messages from yup is simpler

This commit is contained in:
Eric Schultz 2020-10-24 14:18:45 -05:00 committed by Eric Schultz
parent f15a86ace3
commit e8d439d63c
4 changed files with 223 additions and 15 deletions

View file

@ -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 <tituswormer@gmail.com>
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 <tituswormer@gmail.com>
** chalk; version 4.1.0 -- https://github.com/chalk/chalk#readme
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (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) <year> <owner> . 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@gmail.com> (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@gmail.com> (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@gmail.com> (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@gmail.com> (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@gmail.com> (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

View file

@ -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');
}

View file

@ -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();
});
});
});

View file

@ -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
* <ul className="errorListForName">
* {
* errors.name.map((yupFail:YupFail) => (
* <li key={yupFail.id} className="errorItem">{formatMessage(...yupFail.message)</li>
* )
* )
* }
* </ul>
*
*/
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<ToPropResult>[0]) {
this.id = id;
this.values = values;
Object.bind(this, this.message);
}
get message(): Parameters<IntlShape['formatMessage']> {
return [{id:this.id}, this.values];
}
}
type RequiredValueProps = {path:string};
/**
* A spec
*/
type TestOptionsMessageFn<Extra extends Record<string, any> = Record<string, any>, R = any> =
| ((params: Extra & Partial<TestMessageParams> & RequiredValueProps) => R)
export type ToPropResult<Extra extends Record<string, any> = Record<string, any>> = TestOptionsMessageFn<Extra, YupFail>
/**
* A function used for improving typescript safety when manually creating your
* own validation messages
* @param fn a function
*/
export function createMessage<Extra extends Record<string, any> = Record<string, any>>(fn: ToPropResult<Extra>): ToPropResult<Extra> {
return fn;
}
// }
type HoudiniYupLocaleObject = Required<LocaleObject>;
// 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<Extra extends Record<string, any> = Record<string, unknown>>(id: string): ToPropResult<Extra> {
// 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);