remix-validated-form 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. package/.eslintignore +1 -0
  2. package/.eslintrc.js +46 -0
  3. package/.github/workflows/test.yml +35 -0
  4. package/.husky/pre-commit +4 -0
  5. package/.prettierignore +8 -0
  6. package/LICENSE +21 -0
  7. package/README.md +3 -0
  8. package/jest.config.js +5 -0
  9. package/package.json +73 -0
  10. package/src/ValidatedForm.tsx +130 -0
  11. package/src/hooks.ts +27 -0
  12. package/src/index.ts +5 -0
  13. package/src/internal/formContext.ts +18 -0
  14. package/src/internal/util.ts +23 -0
  15. package/src/server.ts +5 -0
  16. package/src/validation/types.ts +12 -0
  17. package/src/validation/validation.test.ts +76 -0
  18. package/src/validation/withYup.ts +37 -0
  19. package/test-app/README.md +53 -0
  20. package/test-app/app/components/Input.tsx +24 -0
  21. package/test-app/app/components/SubmitButton.tsx +18 -0
  22. package/test-app/app/entry.client.tsx +4 -0
  23. package/test-app/app/entry.server.tsx +21 -0
  24. package/test-app/app/root.tsx +246 -0
  25. package/test-app/app/routes/default-values.tsx +34 -0
  26. package/test-app/app/routes/index.tsx +100 -0
  27. package/test-app/app/routes/noscript.tsx +10 -0
  28. package/test-app/app/routes/submission.alt.tsx +6 -0
  29. package/test-app/app/routes/submission.fetcher.tsx +6 -0
  30. package/test-app/app/routes/submission.tsx +47 -0
  31. package/test-app/app/routes/validation.tsx +40 -0
  32. package/test-app/app/styles/dark.css +7 -0
  33. package/test-app/app/styles/demos/about.css +26 -0
  34. package/test-app/app/styles/demos/remix.css +120 -0
  35. package/test-app/app/styles/global.css +98 -0
  36. package/test-app/cypress/fixtures/example.json +5 -0
  37. package/test-app/cypress/integration/default-values.ts +15 -0
  38. package/test-app/cypress/integration/sanity.ts +19 -0
  39. package/test-app/cypress/integration/submission.ts +26 -0
  40. package/test-app/cypress/integration/validation.ts +70 -0
  41. package/test-app/cypress/plugins/config.ts +38 -0
  42. package/test-app/cypress/plugins/index.ts +9 -0
  43. package/test-app/cypress/support/commands/index.ts +13 -0
  44. package/test-app/cypress/support/commands/types.d.ts +11 -0
  45. package/test-app/cypress/support/index.ts +20 -0
  46. package/test-app/cypress/tsconfig.json +11 -0
  47. package/test-app/cypress.json +3 -0
  48. package/test-app/package-lock.json +11675 -0
  49. package/test-app/package.json +40 -0
  50. package/test-app/public/favicon.ico +0 -0
  51. package/test-app/remix.config.js +10 -0
  52. package/test-app/remix.env.d.ts +2 -0
  53. package/test-app/tsconfig.json +18 -0
  54. package/tsconfig.json +15 -0
@@ -0,0 +1,98 @@
1
+ /*
2
+ * You can just delete everything here or keep whatever you like, it's just a
3
+ * quick baseline!
4
+ */
5
+ :root {
6
+ --color-foreground: hsl(0, 0%, 7%);
7
+ --color-background: hsl(0, 0%, 100%);
8
+ --color-links: hsl(213, 100%, 52%);
9
+ --color-links-hover: hsl(213, 100%, 43%);
10
+ --color-border: hsl(0, 0%, 82%);
11
+ --font-body: -apple-system, "Segoe UI", Helvetica Neue, Helvetica, Roboto,
12
+ Arial, sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji";
13
+ }
14
+
15
+ html {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ *,
20
+ *::before,
21
+ *::after {
22
+ box-sizing: inherit;
23
+ }
24
+
25
+ :-moz-focusring {
26
+ outline: auto;
27
+ }
28
+
29
+ :focus {
30
+ outline: var(--color-links) solid 2px;
31
+ outline-offset: 2px;
32
+ }
33
+
34
+ html,
35
+ body {
36
+ padding: 0;
37
+ margin: 0;
38
+ background-color: var(--color-background);
39
+ color: var(--color-foreground);
40
+ }
41
+
42
+ body {
43
+ font-family: var(--font-body);
44
+ line-height: 1.5;
45
+ }
46
+
47
+ a {
48
+ color: var(--color-links);
49
+ text-decoration: none;
50
+ }
51
+
52
+ a:hover {
53
+ color: var(--color-links-hover);
54
+ text-decoration: underline;
55
+ }
56
+
57
+ hr {
58
+ display: block;
59
+ height: 1px;
60
+ border: 0;
61
+ background-color: var(--color-border);
62
+ margin-top: 2rem;
63
+ margin-bottom: 2rem;
64
+ }
65
+
66
+ input:where([type="text"]),
67
+ input:where([type="search"]) {
68
+ display: block;
69
+ border: 1px solid var(--color-border);
70
+ width: 100%;
71
+ font: inherit;
72
+ line-height: 1;
73
+ height: calc(1ch + 1.5em);
74
+ padding-right: 0.5em;
75
+ padding-left: 0.5em;
76
+ background-color: hsl(0 0% 100% / 20%);
77
+ color: var(--color-foreground);
78
+ }
79
+
80
+ .sr-only {
81
+ position: absolute;
82
+ width: 1px;
83
+ height: 1px;
84
+ padding: 0;
85
+ margin: -1px;
86
+ overflow: hidden;
87
+ clip: rect(0, 0, 0, 0);
88
+ white-space: nowrap;
89
+ border-width: 0;
90
+ }
91
+
92
+ .container {
93
+ --gutter: 16px;
94
+ width: 1024px;
95
+ max-width: calc(100% - var(--gutter) * 2);
96
+ margin-right: auto;
97
+ margin-left: auto;
98
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "Using fixtures to represent data",
3
+ "email": "hello@cypress.io",
4
+ "body": "Fixtures are a great way to mock data for responses to routes"
5
+ }
@@ -0,0 +1,15 @@
1
+ describe("Validation", () => {
2
+ it("should propagate default values to inputs", () => {
3
+ cy.visit("/default-values");
4
+ cy.findByLabelText("First Name").should("have.value", "Jane");
5
+ cy.findByLabelText("Last Name").should("have.value", "Doe");
6
+ cy.findByLabelText("Email").should("have.value", "jane.doe@example.com");
7
+ });
8
+
9
+ it("should propagate default values to inputs without JS", () => {
10
+ cy.visitWithoutJs("/default-values");
11
+ cy.findByLabelText("First Name").should("have.value", "Jane");
12
+ cy.findByLabelText("Last Name").should("have.value", "Doe");
13
+ cy.findByLabelText("Email").should("have.value", "jane.doe@example.com");
14
+ });
15
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Some tests just to verify that the test utils in the app actually behave as assumed by other tests
3
+ */
4
+
5
+ describe("Sanity", () => {
6
+ describe("Visiting without JS", () => {
7
+ it("should not show a noscript message when javascript enabled", () => {
8
+ cy.visit("/noscript");
9
+ cy.findByText("Is JS turned on?").should("exist");
10
+ cy.findByText("JS is turned off").should("not.exist");
11
+ });
12
+
13
+ it("should show a noscript message when javascript disabled", () => {
14
+ cy.visitWithoutJs("/noscript");
15
+ cy.findByText("Is JS turned on?").should("exist");
16
+ cy.findByText("JS is turned off").should("exist");
17
+ });
18
+ });
19
+ });
@@ -0,0 +1,26 @@
1
+ describe("Validation", () => {
2
+ it("should show the loading state for the correct submit button", () => {
3
+ cy.visit("/submission");
4
+ cy.findByText("Submit main form").click();
5
+ cy.findByText("Submitting alt form").should("not.exist");
6
+ cy.findByText("Submitting fetcher form").should("not.exist");
7
+ // if we check the one we're expecting last, then the test will correctly fail if the other buttons are in a loading state
8
+ cy.findByText("Submitting main form").should("exist");
9
+
10
+ cy.findByText("Submitting main form").should("not.exist");
11
+
12
+ cy.findByText("Submit alt form").click();
13
+ cy.findByText("Submitting main form").should("not.exist");
14
+ cy.findByText("Submitting fetcher form").should("not.exist");
15
+ cy.findByText("Submitting alt form").should("exist");
16
+
17
+ cy.findByText("Submitting alt form").should("not.exist");
18
+
19
+ cy.findByText("Submit fetcher form").click();
20
+ cy.findByText("Submitting alt form").should("not.exist");
21
+ cy.findByText("Submitting main form").should("not.exist");
22
+ cy.findByText("Submitting fetcher form").should("exist");
23
+
24
+ cy.findByText("Submitting fetcher form").should("not.exist");
25
+ });
26
+ });
@@ -0,0 +1,70 @@
1
+ describe("Validation", () => {
2
+ it("should support validating individual fields", () => {
3
+ cy.visit("/validation");
4
+
5
+ cy.findByLabelText("First Name").focus().blur();
6
+ cy.findByText("First Name is a required field").should("exist");
7
+ cy.findByLabelText("First Name").type("John");
8
+ cy.findByText("First Name is a required field").should("not.exist");
9
+
10
+ cy.findByLabelText("Last Name").focus().blur();
11
+ cy.findByText("Last Name is a required field").should("exist");
12
+ cy.findByLabelText("Last Name").type("Doe");
13
+ cy.findByText("Last Name is a required field").should("not.exist");
14
+
15
+ cy.findByLabelText("Email").focus().blur();
16
+ cy.findByText("Email is a required field").should("exist");
17
+ cy.findByLabelText("Email").type("not an email");
18
+ cy.findByLabelText("Email").blur();
19
+ cy.findByText("Email must be a valid email").should("exist");
20
+
21
+ cy.findByLabelText("Email").clear().type("an.email@example.com").blur();
22
+ cy.findByText("Email must be a valid email").should("not.exist");
23
+
24
+ cy.findByText("Email is a required field").should("not.exist");
25
+ cy.findByText("Last Name is a required field").should("not.exist");
26
+ cy.findByText("First Name is a required field").should("not.exist");
27
+
28
+ cy.findByText("Submit").click();
29
+ cy.findByText("Submitted for John Doe!").should("exist");
30
+ });
31
+
32
+ it("should validate the whole form at once when submit clicked", () => {
33
+ cy.visit("/validation");
34
+
35
+ cy.findByText("Submit").click();
36
+
37
+ cy.findByText("Email is a required field").should("exist");
38
+ cy.findByText("Last Name is a required field").should("exist");
39
+ cy.findByText("First Name is a required field").should("exist");
40
+
41
+ cy.findByLabelText("First Name").type("John");
42
+ cy.findByText("First Name is a required field").should("not.exist");
43
+
44
+ cy.findByLabelText("Last Name").type("Doe");
45
+ cy.findByText("Last Name is a required field").should("not.exist");
46
+
47
+ cy.findByLabelText("Email").type("an.email@example.com").blur();
48
+ cy.findByText("Email is a required field").should("not.exist");
49
+
50
+ cy.findByText("Submit").click();
51
+ cy.findByText("Submitted for John Doe!").should("exist");
52
+ });
53
+
54
+ it("should show validation errors even with JS disabled", () => {
55
+ cy.visitWithoutJs("/validation");
56
+
57
+ cy.findByText("Submit").click();
58
+
59
+ cy.findByText("Email is a required field").should("exist");
60
+ cy.findByText("Last Name is a required field").should("exist");
61
+ cy.findByText("First Name is a required field").should("exist");
62
+
63
+ cy.findByLabelText("First Name").type("John");
64
+ cy.findByLabelText("Last Name").type("Doe");
65
+ cy.findByLabelText("Email").type("an.email@example.com").blur();
66
+
67
+ cy.findByText("Submit").click();
68
+ cy.findByText("Submitted for John Doe!").should("exist");
69
+ });
70
+ });
@@ -0,0 +1,38 @@
1
+ // Plugin taken from Kent C Dodds' remix app
2
+ export default (
3
+ on: Cypress.PluginEvents,
4
+ config: Cypress.PluginConfigOptions
5
+ ) => {
6
+ const port = process.env.PORT ?? "3000";
7
+ const configOverrides: Partial<Cypress.PluginConfigOptions> = {
8
+ baseUrl: `http://localhost:${port}`,
9
+ viewportWidth: 1030,
10
+ viewportHeight: 800,
11
+ integrationFolder: "cypress/integration",
12
+ video: !process.env.CI,
13
+ screenshotOnRunFailure: !process.env.CI,
14
+ };
15
+ Object.assign(config, configOverrides);
16
+
17
+ on("before:browser:launch", (browser, options) => {
18
+ if (browser.name === "chrome") {
19
+ options.args.push(
20
+ "--no-sandbox",
21
+ "--allow-file-access-from-files",
22
+ "--use-fake-ui-for-media-stream",
23
+ "--use-fake-device-for-media-stream",
24
+ "--use-file-for-fake-audio-capture=cypress/fixtures/sample.wav"
25
+ );
26
+ }
27
+ return options;
28
+ });
29
+
30
+ on("task", {
31
+ log(message) {
32
+ console.log(message);
33
+ return null;
34
+ },
35
+ });
36
+
37
+ return config;
38
+ };
@@ -0,0 +1,9 @@
1
+ import configPlugin from "./config";
2
+
3
+ export default (
4
+ on: Cypress.PluginEvents,
5
+ config: Cypress.PluginConfigOptions
6
+ ) => {
7
+ configPlugin(on, config);
8
+ return config;
9
+ };
@@ -0,0 +1,13 @@
1
+ import "@testing-library/cypress/add-commands";
2
+
3
+ Cypress.Commands.add("visitWithoutJs", (url) => {
4
+ const parentDocument = (cy as any).state("window").parent.document;
5
+ const iframe = parentDocument.querySelector(".iframes-container iframe");
6
+ if (false !== Cypress.config("chromeWebSecurity")) {
7
+ throw new TypeError(
8
+ "When you disable script you also have to set 'chromeWebSecurity' in your config to 'false'"
9
+ );
10
+ }
11
+ iframe.sandbox = "allow-forms";
12
+ return cy.visit(url);
13
+ });
@@ -0,0 +1,11 @@
1
+ /// <reference types="cypress" />
2
+
3
+ declare namespace Cypress {
4
+ interface Chainable {
5
+ /**
6
+ * Custom command to visit a url with javascript disabled.
7
+ * @example cy.visitWithoutJs("/teacher")
8
+ */
9
+ visitWithoutJs(url: string): void;
10
+ }
11
+ }
@@ -0,0 +1,20 @@
1
+ // ***********************************************************
2
+ // This example support/index.js is processed and
3
+ // loaded automatically before your test files.
4
+ //
5
+ // This is a great place to put global configuration and
6
+ // behavior that modifies Cypress.
7
+ //
8
+ // You can change the location of this file or turn off
9
+ // automatically serving support files with the
10
+ // 'supportFile' configuration option.
11
+ //
12
+ // You can read more here:
13
+ // https://on.cypress.io/configuration
14
+ // ***********************************************************
15
+
16
+ // Import commands.js using ES2015 syntax:
17
+ import "./commands";
18
+
19
+ // Alternatively you can use CommonJS syntax:
20
+ // require('./commands')
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": ["cypress", "@testing-library/cypress", "node"],
6
+ "baseUrl": "..",
7
+ "paths": { "~/*": ["app/*"] }
8
+ },
9
+ "include": ["./**/*"],
10
+ "exclude": []
11
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "chromeWebSecurity": false
3
+ }