From 04d18460970d0ff5c2bd29623dd78845338e9427 Mon Sep 17 00:00:00 2001 From: Benjamin Skeen Date: Wed, 13 Sep 2023 10:19:40 -0600 Subject: [PATCH 1/9] I added some advanced recurring functionality. --- jest.config.js | 3 + package-lock.json | 511 ++++++---- package.json | 4 +- src/calendars/FullNoteCalendar.test.ts | 112 +++ src/calendars/FullNoteCalendar.ts | 25 +- src/main.ts | 77 +- src/ui/components/EditEvent.tsx | 442 ++++----- src/ui/components/EditEventRecurrence.test.ts | 691 ++++++++++++++ src/ui/components/EditEventRecurrence.tsx | 870 ++++++++++++++++++ src/ui/overrides.css | 8 + 10 files changed, 2289 insertions(+), 454 deletions(-) create mode 100644 src/ui/components/EditEventRecurrence.test.ts create mode 100644 src/ui/components/EditEventRecurrence.tsx diff --git a/jest.config.js b/jest.config.js index 34a9dd82..02310684 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,7 @@ module.exports = { preset: "ts-jest", testEnvironment: "node", + transform: { + "^.+\\.tsx?$": "ts-jest" + } }; diff --git a/package-lock.json b/package-lock.json index 3b98744b..7f1e27c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "@types/node": "^16.11.6", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", + "@types/react-test-renderer": "^18.0.1", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "builtin-modules": "^3.2.0", @@ -51,10 +52,21 @@ "jest": "^29.3.1", "obsidian": "^0.16.3", "prettier": "^2.5.1", + "react-test-renderer": "^17.0.2", "ts-jest": "^29.0.3", "tslib": "2.3.1", "typescript": "4.4.4", - "zod-fast-check": "^0.9.0" + "zod-fast-check": "^0.10.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@ampproject/remapping": { @@ -675,19 +687,19 @@ "dev": true }, "node_modules/@codemirror/state": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", - "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", + "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==", "peer": true }, "node_modules/@codemirror/view": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.1.tgz", - "integrity": "sha512-bzfSjJn9dAADVpabLKWKNmMG4ibyTV2e3eOGowjElNPTdTkSbi6ixPYHm2u0ADcETfKsi2/R84Rkmi91dH9yEg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.18.1.tgz", + "integrity": "sha512-xcsXcMkIMd7l3WZEWoc4ljteAiqzxb5gVerRxk5132p5cLix6rTydWTQjsj2oxORepfsrwy1fC4r20iMa9plrg==", "peer": true, "dependencies": { "@codemirror/state": "^6.1.4", - "style-mod": "^4.0.0", + "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, @@ -1021,16 +1033,42 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", - "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -1046,9 +1084,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", - "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", "dev": true, "peer": true, "engines": { @@ -1142,9 +1180,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "peer": true, "dependencies": { @@ -2254,6 +2292,15 @@ "@types/react": "^17" } }, + "node_modules/@types/react-test-renderer": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.1.tgz", + "integrity": "sha512-LjEF+jTUCjzd+Qq4eWqsmZvEWPA/l4L0my+YWN5US8Fo3wZOMiyrpBshHDFbkO8usjdO1B430mEWNU/i1MF7Qg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -2529,9 +2576,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -3893,27 +3940,28 @@ } }, "node_modules/eslint": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", - "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, "peer": true, "dependencies": { - "@eslint/eslintrc": "^2.0.0", - "@eslint/js": "8.35.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3921,23 +3969,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -3991,18 +4035,21 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "peer": true, "dependencies": { @@ -4011,6 +4058,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/estraverse": { @@ -4024,15 +4074,15 @@ } }, "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "peer": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4386,17 +4436,18 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", "dev": true, "peer": true, "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.7", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=12.0.0" } }, "node_modules/flatted": { @@ -4550,9 +4601,9 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "peer": true, "dependencies": { @@ -4613,6 +4664,13 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "peer": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6058,17 +6116,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6098,6 +6145,13 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "peer": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6140,6 +6194,16 @@ "node": ">= 0.6" } }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7642,18 +7706,18 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "peer": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -8146,6 +8210,40 @@ "node": ">=0.10.0" } }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-test-renderer": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", + "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^17.0.2", + "react-shallow-renderer": "^16.13.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8718,9 +8816,9 @@ } }, "node_modules/style-mod": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", - "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", + "integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==", "peer": true }, "node_modules/style-to-object": { @@ -9329,9 +9427,9 @@ } }, "node_modules/w3c-keyname": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", - "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "peer": true }, "node_modules/walker": { @@ -9474,16 +9572,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -9607,12 +9695,12 @@ } }, "node_modules/zod-fast-check": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/zod-fast-check/-/zod-fast-check-0.9.0.tgz", - "integrity": "sha512-7N56zNAO7HabbIETlCofd8e94ZRBulo9gx8qAC5AC+yTVo++WUKqi21fNyIWHtM46Xl0iTT7ucz0blMoCTz5jg==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/zod-fast-check/-/zod-fast-check-0.10.0.tgz", + "integrity": "sha512-3AjwS/s0jB1gkbgslmSSjI+MNoT8T9R+YiM9RruyB8mwZmiVfvA8UDfu7jkwzPwSHfyyqGGnH0UL2Hag7oj18w==", "dev": true, "peerDependencies": { - "fast-check": "^2.23.0", + "fast-check": ">2.23.0 <4.0.0", "zod": "^3.18.0" } }, @@ -9627,6 +9715,13 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "peer": true + }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -10083,19 +10178,19 @@ "dev": true }, "@codemirror/state": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", - "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.1.tgz", + "integrity": "sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==", "peer": true }, "@codemirror/view": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.1.tgz", - "integrity": "sha512-bzfSjJn9dAADVpabLKWKNmMG4ibyTV2e3eOGowjElNPTdTkSbi6ixPYHm2u0ADcETfKsi2/R84Rkmi91dH9yEg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.18.1.tgz", + "integrity": "sha512-xcsXcMkIMd7l3WZEWoc4ljteAiqzxb5gVerRxk5132p5cLix6rTydWTQjsj2oxORepfsrwy1fC4r20iMa9plrg==", "peer": true, "requires": { "@codemirror/state": "^6.1.4", - "style-mod": "^4.0.0", + "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, @@ -10231,16 +10326,33 @@ "integrity": "sha512-JOOxw49BVZx2/5tW3FqkdjSD/5gXYeVGPDcB0lvap0gLQshkh1Nyel1QazC+wNxus3xPlsYAgqU1BUmrmCvWtw==", "optional": true }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "peer": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "peer": true + }, "@eslint/eslintrc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz", - "integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "peer": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -10250,9 +10362,9 @@ } }, "@eslint/js": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz", - "integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", "dev": true, "peer": true }, @@ -10340,9 +10452,9 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "peer": true, "requires": { @@ -11203,6 +11315,15 @@ "@types/react": "^17" } }, + "@types/react-test-renderer": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.1.tgz", + "integrity": "sha512-LjEF+jTUCjzd+Qq4eWqsmZvEWPA/l4L0my+YWN5US8Fo3wZOMiyrpBshHDFbkO8usjdO1B430mEWNU/i1MF7Qg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", @@ -11377,9 +11498,9 @@ } }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" }, "acorn-jsx": { "version": "5.3.2", @@ -12292,27 +12413,28 @@ "peer": true }, "eslint": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", - "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", "dev": true, "peer": true, "requires": { - "@eslint/eslintrc": "^2.0.0", - "@eslint/js": "8.35.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -12320,30 +12442,26 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "dependencies": { "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "peer": true, "requires": { @@ -12388,21 +12506,21 @@ } }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "peer": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -12661,13 +12779,14 @@ } }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", "dev": true, "peer": true, "requires": { - "flatted": "^3.1.0", + "flatted": "^3.2.7", + "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, @@ -12776,9 +12895,9 @@ } }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "peer": true, "requires": { @@ -12824,6 +12943,13 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "peer": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -13864,13 +13990,6 @@ } } }, - "js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true, - "peer": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -13891,6 +14010,13 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "peer": true + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -13924,6 +14050,16 @@ "tsscmp": "1.0.6" } }, + "keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "peer": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -14934,18 +15070,18 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "peer": true, "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "p-limit": { @@ -15276,6 +15412,36 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==" }, + "react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + } + }, + "react-test-renderer": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", + "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "react-is": "^17.0.2", + "react-shallow-renderer": "^16.13.1", + "scheduler": "^0.20.2" + }, + "dependencies": { + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + } + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -15680,9 +15846,9 @@ "dev": true }, "style-mod": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", - "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", + "integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==", "peer": true }, "style-to-object": { @@ -16069,9 +16235,9 @@ } }, "w3c-keyname": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", - "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "peer": true }, "walker": { @@ -16170,13 +16336,6 @@ } } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "peer": true - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -16263,9 +16422,9 @@ "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==" }, "zod-fast-check": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/zod-fast-check/-/zod-fast-check-0.9.0.tgz", - "integrity": "sha512-7N56zNAO7HabbIETlCofd8e94ZRBulo9gx8qAC5AC+yTVo++WUKqi21fNyIWHtM46Xl0iTT7ucz0blMoCTz5jg==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/zod-fast-check/-/zod-fast-check-0.10.0.tgz", + "integrity": "sha512-3AjwS/s0jB1gkbgslmSSjI+MNoT8T9R+YiM9RruyB8mwZmiVfvA8UDfu7jkwzPwSHfyyqGGnH0UL2Hag7oj18w==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index e466f11d..dc72d5b9 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/node": "^16.11.6", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", + "@types/react-test-renderer": "^18.0.1", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "builtin-modules": "^3.2.0", @@ -40,10 +41,11 @@ "jest": "^29.3.1", "obsidian": "^0.16.3", "prettier": "^2.5.1", + "react-test-renderer": "^17.0.2", "ts-jest": "^29.0.3", "tslib": "2.3.1", "typescript": "4.4.4", - "zod-fast-check": "^0.9.0" + "zod-fast-check": "^0.10.0" }, "dependencies": { "@fullcalendar/core": "^5.10.1", diff --git a/src/calendars/FullNoteCalendar.test.ts b/src/calendars/FullNoteCalendar.test.ts index 85cbef44..7a36bc42 100644 --- a/src/calendars/FullNoteCalendar.test.ts +++ b/src/calendars/FullNoteCalendar.test.ts @@ -324,4 +324,116 @@ describe("Note Calendar Tests", () => { // .calls[0]; // expect(file.path).toBe(join("events", filename)); // }); + + it("creates an rrule event", async () => { + const obsidian = makeApp(MockAppBuilder.make().done()); + const calendar = new FullNoteCalendar(obsidian, color, dirName); + const event = { + type: "rrule", + title: "Test Event", + startDate: "2023-09-12", + rrule: "DTSTART:20230912T110000Z\nRRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU", + skipDates: ["2023-09-19"], + allDay: false, + startTime: "11:00", + endTime: "12:30", + }; + + (obsidian.create as jest.Mock).mockReturnValue({ + path: join(dirName, "2022-01-01 Test Event.md"), + }); + const { lineNumber } = await calendar.createEvent(parseEvent(event)); + expect(lineNumber).toBeUndefined(); + expect(obsidian.create).toHaveBeenCalledTimes(1); + const returns = (obsidian.create as jest.Mock).mock.calls[0]; + console.warn(returns); + expect(returns).toMatchInlineSnapshot(` + [ + "events/(every week on Tuesday for 30 times) Test Event.md", + "--- + title: Test Event + allDay: false + startTime: 11:00 + endTime: 12:30 + type: rrule + startDate: 2023-09-12 + rrule: |- + DTSTART:20230912T110000Z + RRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU + skipDates: [2023-09-19] + --- + ", + ] + `); + }); + it("modifies an rrule event", async () => { + const rawEvent = { + type: "rrule", + title: "Test Event", + startDate: "2023-09-12", + rrule: "DTSTART:20230912T110000Z\nRRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU", + skipDates: ["2023-09-19"], + allDay: false, + startTime: "11:00", + endTime: "12:30", + }; + const event = parseEvent(rawEvent); + const filename = "(every week on Tuesday for 30 times) Test Event.md"; + const obsidian = makeApp( + MockAppBuilder.make() + .folder( + new MockAppBuilder("events").file( + filename, + new FileBuilder().frontmatter(event) + ) + ) + .done() + ); + const calendar = new FullNoteCalendar(obsidian, color, dirName); + + const firstFile = obsidian.getAbstractFileByPath( + join("events", filename) + ) as TFile; + + const contents = await obsidian.read(firstFile); + + const mockFn = jest.fn(); + await calendar.modifyEvent( + { path: join("events", filename), lineNumber: undefined }, + // @ts-ignore + parseEvent({ + ...rawEvent, + rrule: "DTSTART:20230912T110000Z\nRRULE:FREQ=MONTHLY;COUNT=5;INTERVAL=2;BYDAY=TU;BYSETPOS=1", + }), + mockFn + ); + const newFilename = + "events/(every 2 months on Tuesday for 5 times) Test Event.md"; + // TODO: make the third param a mock that we can inspect + const newLoc = mockFn.mock.calls[0][0]; + expect(newLoc.file.path).toBe(newFilename); + expect(newLoc.lineNumber).toBeUndefined(); + + expect(obsidian.rewrite).toHaveReturnedTimes(1); + const [file, rewriteCallback] = (obsidian.rewrite as jest.Mock).mock + .calls[0]; + expect(file.path).toBe(join("events", filename)); + + expect(rewriteCallback(contents)).toMatchInlineSnapshot(` + "--- + title: Test Event + allDay: false + startTime: 11:00 + endTime: 12:30 + type: rrule + startDate: 2023-09-12 + rrule: |- + DTSTART:20230912T110000Z + RRULE:FREQ=MONTHLY;COUNT=5;INTERVAL=2;BYDAY=TU;BYSETPOS=1 + RRULE:FREQ=WEEKLY;COUNT=30;INTERVAL=1;BYDAY=TU + skipDates: [2023-09-19] + --- + " + `); + }); }); diff --git a/src/calendars/FullNoteCalendar.ts b/src/calendars/FullNoteCalendar.ts index c41e0146..b06f65df 100644 --- a/src/calendars/FullNoteCalendar.ts +++ b/src/calendars/FullNoteCalendar.ts @@ -80,7 +80,17 @@ function stringifyYamlLine( k: string | number | symbol, v: PrintableAtom ): string { - return `${String(k)}: ${stringifyYamlAtom(v)}`; + let stringifiedAtom: string; + if (k === "rrule" && typeof v === "string") { + stringifiedAtom = "|-"; + + const indentation = "\n "; + const replacedValue = v.replace("\n", indentation); + stringifiedAtom += `${indentation}${replacedValue}`; + } else { + stringifiedAtom = stringifyYamlAtom(v); + } + return `${String(k)}: ${stringifiedAtom}`; } function newFrontmatter(fields: Partial): string { @@ -109,7 +119,18 @@ function modifyFrontmatterString( const linesAdded: Set = new Set(); // Modify rows in-place. for (let i = 0; i < frontmatter.length; i++) { - const line: string = frontmatter[i]; + let line: string = frontmatter[i]; + if (line.endsWith("|-")) { + let j = i + 1; + while ( + j < frontmatter.length && + frontmatter[j].startsWith(" ") + ) { + line += `\n${frontmatter[j]}`; + i++; + j++; + } + } const obj: Record | null = parseYaml(line); if (!obj) { continue; diff --git a/src/main.ts b/src/main.ts index db16e9c4..558eda48 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,41 +22,47 @@ import CalDAVCalendar from "./calendars/CalDAVCalendar"; export default class FullCalendarPlugin extends Plugin { settings: FullCalendarSettings = DEFAULT_SETTINGS; - cache: EventCache = new EventCache({ - local: (info) => - info.type === "local" - ? new FullNoteCalendar( - new ObsidianIO(this.app), - info.color, - info.directory - ) - : null, - dailynote: (info) => - info.type === "dailynote" - ? new DailyNoteCalendar( - new ObsidianIO(this.app), - info.color, - info.heading - ) - : null, - ical: (info) => - info.type === "ical" ? new ICSCalendar(info.color, info.url) : null, - caldav: (info) => - info.type === "caldav" - ? new CalDAVCalendar( - info.color, - info.name, - { - type: "basic", - username: info.username, - password: info.password, - }, - info.url, - info.homeUrl - ) - : null, - FOR_TEST_ONLY: () => null, - }); + cache: EventCache = (() => { + console.debug("Event Cache Creation"); + + return new EventCache({ + local: (info) => + info.type === "local" + ? new FullNoteCalendar( + new ObsidianIO(this.app), + info.color, + info.directory + ) + : null, + dailynote: (info) => + info.type === "dailynote" + ? new DailyNoteCalendar( + new ObsidianIO(this.app), + info.color, + info.heading + ) + : null, + ical: (info) => + info.type === "ical" + ? new ICSCalendar(info.color, info.url) + : null, + caldav: (info) => + info.type === "caldav" + ? new CalDAVCalendar( + info.color, + info.name, + { + type: "basic", + username: info.username, + password: info.password, + }, + info.url, + info.homeUrl + ) + : null, + FOR_TEST_ONLY: () => null, + }); + })(); renderCalendar = renderCalendar; processFrontmatter = toEventInput; @@ -78,6 +84,7 @@ export default class FullCalendarPlugin extends Plugin { } } async onload() { + console.debug("On Load called..."); await this.loadSettings(); this.cache.reset(this.settings.calendarSources); diff --git a/src/ui/components/EditEvent.tsx b/src/ui/components/EditEvent.tsx index 773a2431..575daddc 100644 --- a/src/ui/components/EditEvent.tsx +++ b/src/ui/components/EditEvent.tsx @@ -2,6 +2,8 @@ import { DateTime } from "luxon"; import * as React from "react"; import { useEffect, useRef, useState } from "react"; import { CalendarInfo, OFCEvent } from "../../types"; +import { RRule, rrulestr } from "rrule"; +import { EditEventRecurrence } from "./EditEventRecurrence"; function makeChangeListener( setState: React.Dispatch>, @@ -10,69 +12,14 @@ function makeChangeListener( return (e) => setState(fromString(e.target.value)); } -interface DayChoiceProps { - code: string; - label: string; - isSelected: boolean; - onClick: (code: string) => void; -} -const DayChoice = ({ code, label, isSelected, onClick }: DayChoiceProps) => ( - -); - const DAY_MAP = { - U: "Sunday", - M: "Monday", - T: "Tuesday", - W: "Wednesday", - R: "Thursday", - F: "Friday", - S: "Saturday", -}; - -const DaySelect = ({ - value: days, - onChange, -}: { - value: string[]; - onChange: (days: string[]) => void; -}) => { - return ( -
- {Object.entries(DAY_MAP).map(([code, label]) => ( - - days.includes(code) - ? onChange(days.filter((c) => c !== code)) - : onChange([code, ...days]) - } - /> - ))} -
- ); + U: RRule.SU.weekday, + M: RRule.MO.weekday, + T: RRule.TU.weekday, + W: RRule.WE.weekday, + R: RRule.TH.weekday, + F: RRule.FR.weekday, + S: RRule.SA.weekday, }; interface EditEventProps { @@ -96,17 +43,17 @@ export const EditEvent = ({ calendars, defaultCalendarIndex, }: EditEventProps) => { - const [date, setDate] = useState( - initialEvent - ? initialEvent.type === "single" - ? initialEvent.date - : initialEvent.type === "recurring" - ? initialEvent.startRecur - : initialEvent.type === "rrule" - ? initialEvent.startDate - : "" + const initialDate = initialEvent + ? initialEvent.type === "single" + ? initialEvent.date + : initialEvent.type === "recurring" + ? initialEvent.startRecur + : initialEvent.type === "rrule" + ? initialEvent.startDate : "" - ); + : ""; + + const [date, setDate] = useState(initialDate); const [endDate, setEndDate] = useState( initialEvent && initialEvent.type === "single" ? initialEvent.endDate @@ -125,14 +72,44 @@ export const EditEvent = ({ const [startTime, setStartTime] = useState(initialStartTime); const [endTime, setEndTime] = useState(initialEndTime); const [title, setTitle] = useState(initialEvent?.title || ""); + // const [isRecurring, setIsRecurring] = useState( + // initialEvent?.type === "recurring" || false + // ); + const [isRecurring, setIsRecurring] = useState( - initialEvent?.type === "recurring" || false + initialEvent?.type === "rrule" || + initialEvent?.type === "recurring" || + false ); - const [endRecur, setEndRecur] = useState(""); - const [daysOfWeek, setDaysOfWeek] = useState( - (initialEvent?.type === "recurring" ? initialEvent.daysOfWeek : []) || - [] + const parsedDate = initialDate + ? DateTime.fromFormat(initialDate, "yyyy-MM-dd") + : DateTime.now(); + console.log("parsedDate:", parsedDate); + + const [recurringRule, setRecurringRule] = useState( + (initialEvent?.type === "rrule" && + initialEvent?.rrule && + rrulestr(initialEvent.rrule)) || + new RRule({ + freq: RRule.WEEKLY, + interval: 1, + dtstart: DateTime.fromObject({ + ...parsedDate.toObject(), + hour: Number(startTime.slice(0, 2)), + minute: Number(startTime.slice(3)), + }).toJSDate(), + byweekday: + initialEvent?.type === "recurring" + ? initialEvent.daysOfWeek?.map( + (value) => DAY_MAP[value] + ) + : [parsedDate.weekday - 1], + until: + initialEvent?.type === "recurring" && initialEvent.endRecur + ? DateTime.fromISO(initialEvent.endRecur).toJSDate() + : undefined, + }) ); const [allDay, setAllDay] = useState(initialEvent?.allDay || false); @@ -170,18 +147,12 @@ export const EditEvent = ({ : { allDay: false, startTime: startTime || "", endTime }), ...(isRecurring ? { - type: "recurring", - daysOfWeek: daysOfWeek as ( - | "U" - | "M" - | "T" - | "W" - | "R" - | "F" - | "S" - )[], - startRecur: date || undefined, - endRecur: endRecur || undefined, + type: "rrule", + rrule: recurringRule.toString(), + startDate: DateTime.fromJSDate( + recurringRule.options.dtstart ?? date + ).toISODate(), + skipDates: [], } : { type: "single", @@ -203,169 +174,160 @@ export const EditEvent = ({
-

- x)} - /> -

-

- -

-

- {!isRecurring && ( +

+

x)} - /> - )} - - {allDay ? ( - <> - ) : ( - <> - x - )} - /> - - - x - )} - /> - - )} -

-

- - setAllDay(e.target.checked)} - type="checkbox" - /> -

-

- - setIsRecurring(e.target.checked)} - type="checkbox" - /> -

- - {isRecurring && ( - <> - x)} /> -

- Starts recurring +

+

+ +

+

+ {!isRecurring && ( x)} /> - and stops recurring - x - )} - /> -

- - )} -

- - { - setIsTask(e.target.checked); - }} - type="checkbox" - /> -

+ )} - {isTask && ( - <> - + {allDay ? ( + <> + ) : ( + <> + x + )} + /> + - + x + )} + /> + + )} +

+

+ - setComplete( - e.target.checked - ? DateTime.now().toISO() - : false - ) - } + id="allDay" + checked={allDay} + onChange={(e) => setAllDay(e.target.checked)} + type="checkbox" + /> +

+ +

+ + { + setIsRecurring(e.target.checked); + }} type="checkbox" /> - - )} +

+ + {isRecurring && ( + + )} + +

+ + { + setIsTask(e.target.checked); + }} + type="checkbox" + /> +

+ + {isTask && ( + <> + + + setComplete( + e.target.checked + ? DateTime.now().toISO() + : false + ) + } + type="checkbox" + /> + + )} +

{ + let currentInfo: RecurrenceInfo; + let options: Partial = { + freq: RRule.DAILY, + dtstart: new Date(), + interval: 1, + wkst: RRule.SU, + count: 1, + until: new Date(), + tzid: "UTC", + bysetpos: [1], + bymonth: [1], + bymonthday: [2], + bynmonthday: [3], + byyearday: [20], + byweekno: [1], + byweekday: [RRule.WE], + bynweekday: [[1]], + byhour: [2], + byminute: [42], + bysecond: [21], + byeaster: 1, + }; + + let dateStats: DateStats; + + let hasPropsResult: boolean; + let optionsResult: Partial; + let displayResult: string; + + const getRecurrenceInfo = (type: MonthYearRecurrenceType) => { + currentInfo = + MONTH_RECURRENCE_INFO.find( + (info) => info.recurrenceType === type + ) ?? MONTH_RECURRENCE_INFO[0]; + }; + + const actHasProps = () => { + hasPropsResult = currentInfo.hasProps(options); + }; + + const actGetProps = () => { + optionsResult = currentInfo.getProps(dateStats); + }; + + const actFilterProps = () => { + optionsResult = currentInfo.filterProps(options); + }; + + const actGetDisplay = () => { + displayResult = currentInfo.getDisplay(dateStats); + }; + + describe("MONTH_RECURRENCE_INFO", () => { + describe("dayOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayOfMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + bymonthday: [1], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + bymonthday: [-1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + bymonthday: 1, + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = {}; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + bymonthday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats month day in the options", () => { + expect(optionsResult.bymonthday).toMatchObject([1]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return the bymonthday option in the options", () => { + expect(Object.keys(optionsResult)).toMatchObject([ + "bymonthday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 12th day of the month" + ); + }); + }); + }); + }); + + describe("dayBeforeEndOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + bymonthday: [1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + bymonthday: [-1], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + bymonthday: 1, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = {}; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + bymonthday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats daysBeforeEnd in the options", () => { + expect(optionsResult.bymonthday).toMatchObject([-30]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return the bymonthday option in the options", () => { + expect(Object.keys(optionsResult)).toMatchObject([ + "bymonthday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 19th to last day of the month" + ); + }); + }); + }); + }); + + describe("dayBeforeEndOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.weekdayInMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdayInMonth in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdayInMonth, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return two keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 2nd Tuesday of the month" + ); + }); + }); + }); + }); + + describe("dayBeforeEndOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo( + MonthYearRecurrenceType.weekdayBeforeEndOfMonth + ); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdaysFromMonthEnd in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdaysFromMonthEnd * -1, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return two keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 3rd to last Tuesday of the month" + ); + }); + }); + }); + }); + }); +}); diff --git a/src/ui/components/EditEventRecurrence.tsx b/src/ui/components/EditEventRecurrence.tsx new file mode 100644 index 00000000..c48ebc15 --- /dev/null +++ b/src/ui/components/EditEventRecurrence.tsx @@ -0,0 +1,870 @@ +import { RRule, Weekday, datetime, ByWeekday, Options } from "rrule"; +import * as React from "react"; +import { useCallback, useState } from "react"; +import { DateTime } from "luxon"; + +interface EditEventRecurrenceProps { + recurrence?: RRule; + startTime?: string; + startDate?: string; + onChange?: (recurrence: RRule) => void; +} + +const ALL_FREQUENCIES = [ + RRule.DAILY, + RRule.WEEKLY, + RRule.MONTHLY, + RRule.YEARLY, +]; + +const FREQ_MAP: { [key: number]: string } = { + [RRule.DAILY]: "Day(s)", + [RRule.WEEKLY]: "Week(s)", + [RRule.MONTHLY]: "Month(s)", + [RRule.YEARLY]: "Year(s)", +}; + +const convertWeekday = (day: ByWeekday) => { + if (typeof day === "string") { + return Weekday.fromStr(day).weekday; + } + + if (Number.isNumber(day)) { + return day; + } + + return day.weekday; +}; + +export const EditEventRecurrence = ({ + recurrence, + startTime, + startDate, + onChange, +}: EditEventRecurrenceProps) => { + const splitStartTime = (startTime ?? "00:00").split(":"); + const startHour = splitStartTime[0]; + const startMinute = splitStartTime[1]; + + const currentDate = DateTime.now(); + + const defaultStartDate = DateTime.fromJSDate( + startDate + ? datetime( + Number(startDate.slice(0, 4)), + Number(startDate.slice(5, 7)), + Number(startDate.slice(8)) + ) + : datetime(currentDate.year, currentDate.month, currentDate.day) + ).toUTC(); + + const options = + recurrence?.origOptions && + "freq" in recurrence.origOptions && + "interval" in recurrence.origOptions + ? recurrence.origOptions + : ({ + freq: RRule.WEEKLY, + interval: 1, + dtstart: defaultStartDate.toJSDate(), + byweekday: [defaultStartDate.weekday - 1], + } as Options); + + console.log("original:", recurrence?.origOptions); + + const currentStartDate = options.dtstart + ? DateTime.fromJSDate(options.dtstart).toUTC() + : defaultStartDate; + + const [defaultInterval, setDefaultInterval] = useState( + options.interval ?? 1 + ); + const [defaultEndCount, setDefaultEndCount] = useState(options.count ?? 1); + const [defaultEndDate, setDefaultEndDate] = useState( + options.until ?? + options.dtstart ?? + DateTime.fromISO(startDate ?? DateTime.now().toISODate()) + .toUTC() + .toJSDate() + ); + + let defaultExtraProps: Partial = {}; + if ((options.freq ?? RRule.WEEKLY) in RECURRENCE_INFO_MAP) { + const recurrenceInfo = + RECURRENCE_INFO_MAP[options.freq ?? RRule.MONTHLY]; + + const currentRecurrenceInfo = recurrenceInfo.find((info) => + info.hasProps(options) + ); + + if (currentRecurrenceInfo) { + defaultExtraProps = currentRecurrenceInfo.filterProps(options); + } + } + + const [currentExtraProps, setCurrentExtraProps] = + useState(defaultExtraProps); + + const [endType, setEndType] = useState( + recurrence?.options.until + ? "endDate" + : recurrence?.options.count + ? "endCount" + : "endNever" + ); + + const currentEndDate = DateTime.fromJSDate( + options.until ?? defaultEndDate ?? options.dtstart ?? new Date() + ); + + const handleChange = useCallback( + ( + updatedOptions: Partial, + includeExtra: boolean = true, + updatedEndType?: string + ) => { + const freq = updatedOptions.freq ?? options.freq; + const interval = options.interval; + const dtstart = options.dtstart; + let otherOptions: Partial = {}; + + console.log("Other:", otherOptions); + console.log("Updated:", updatedOptions); + console.log("Include Extra", includeExtra); + + if ((updatedEndType ?? endType) === "endDate") { + otherOptions.until = currentEndDate.toJSDate(); + } else if ((updatedEndType ?? endType) === "endCount") { + otherOptions.count = updatedOptions.count ?? options.count; + } + + if (freq === RRule.WEEKLY) { + otherOptions.byweekday = + updatedOptions.byweekday ?? options.byweekday; + } + + if (includeExtra) { + otherOptions = { + ...otherOptions, + ...currentExtraProps, + }; + } + + const newProps = { + freq, + interval, + dtstart, + ...otherOptions, + ...updatedOptions, + }; + + console.log("newProps", newProps); + + onChange?.(new RRule(newProps)); + }, + [onChange, options, endType, currentEndDate, currentExtraProps] + ); + + return ( + <> +

+ Every + { + const interval = Number(element.target.value); + setDefaultInterval(interval); + handleChange({ + interval, + }); + }} + /> + +

+ {options.freq === RRule.WEEKLY && ( + { + handleChange({ + byweekday: days, + }); + }} + /> + )} + {(options.freq === RRule.MONTHLY || + options.freq === RRule.YEARLY) && ( + { + setCurrentExtraProps(options); + handleChange(options, false); + }} + /> + )} +

+ + { + handleChange({ + dtstart: DateTime.fromISO(element.target.value) + .toUTC() + .set({ + hour: Number(startHour), + minute: Number(startMinute), + second: 0, + }) + .toJSDate(), + }); + }} + /> +

+

Ending:

+

+ { + setEndType("endNever"); + handleChange({}, true, "endNever"); + }} + /> + +

+

+ { + setEndType("endDate"); + handleChange({}); + }} + /> + + { + const value = DateTime.fromISO( + element.target.value + ).toUTC(); + setDefaultEndDate(value.toJSDate()); + handleChange( + { + until: value.toJSDate(), + }, + true, + "endDate" + ); + }} + /> +

+

+ { + setEndType("endCount"); + handleChange({}, true, "endCount"); + }} + /> + + { + const value = Number(element.target.value); + setDefaultEndCount(value); + handleChange({ + count: value, + }); + }} + className="fc-edit-control" + disabled={endType !== "endCount"} + /> + +

+ + ); +}; + +interface DayChoiceProps { + code: number; + label: string; + isSelected: boolean; + onClick: (code: number) => void; +} +const DayChoice = ({ code, label, isSelected, onClick }: DayChoiceProps) => ( + +); + +const DAYS_OF_WEEK = [ + RRule.SU, + RRule.MO, + RRule.TU, + RRule.WE, + RRule.TH, + RRule.FR, + RRule.SA, +]; + +const DaySelect = ({ + value, + onChange, +}: { + value: number[]; + onChange: (days: number[]) => void; +}) => { + return ( +
+ {DAYS_OF_WEEK.map((day) => { + const code = day.weekday; + return ( + { + console.log("Day Switched:", value, code); + value.includes(code) + ? onChange(value.filter((c) => c !== code)) + : onChange([code, ...value]); + }} + /> + ); + })} +
+ ); +}; + +interface MonthSelectProps { + startDate: DateTime; + options: Partial; + onChange: (options: Partial) => void; +} + +export enum MonthYearRecurrenceType { + dayOfMonth = 0, + dayBeforeEndOfMonth = 1, + weekdayInMonth = 2, + weekdayBeforeEndOfMonth = 3, + dayOfYear = 4, + dayBeforeEndOfYear = 5, + weekdayInYear = 6, + weekdayBeforeEndOfYear = 7, +} + +export interface DateStats { + monthDay: number; + weekday: number; + daysUntilEndMonth: number; + weekdayInMonth: number; + weekdaysFromMonthEnd: number; + dayName: string; + month: number; + monthName: string; + yearDay: number; + daysUntilEndYear: number; + weekdayInYear: number; + weekdaysFromYearEnd: number; +} + +export const getDateStats = (date: DateTime): DateStats => { + const monthDay = Number(date.day); + const daysInMonth = date.daysInMonth; + const yearDay = date.ordinal; + const daysInYear = date.daysInYear; + + return { + monthDay, + weekday: date.weekday - 1, + daysUntilEndMonth: daysInMonth - monthDay + 1, + weekdayInMonth: Math.floor((monthDay - 1) / 7) + 1, + weekdaysFromMonthEnd: Math.floor((daysInMonth - monthDay) / 7) + 1, + dayName: date.weekdayLong, + month: date.month, + monthName: date.monthLong, + yearDay, + daysUntilEndYear: daysInYear - yearDay + 1, + weekdayInYear: Math.floor((yearDay - 1) / 7) + 1, + weekdaysFromYearEnd: Math.floor((daysInYear - yearDay) / 7) + 1, + }; +}; + +const formatOrdinalNumber = (value: number): string => { + const isBetween10And20 = value > 10 && value < 20; + const onesDigit = value % 10; + if (onesDigit === 1 && !isBetween10And20) { + return `${value}st`; + } + + if (onesDigit === 2 && !isBetween10And20) { + return `${value}nd`; + } + + if (onesDigit === 3 && !isBetween10And20) { + return `${value}rd`; + } + + return `${value}th`; +}; + +const formatLastOrdinalNumber = (value: number): string => { + if (value === 1) { + return "last"; + } + + return `${formatOrdinalNumber(value)} to last`; +}; + +export interface RecurrenceInfo { + recurrenceType: MonthYearRecurrenceType; + hasProps: (options: Partial) => boolean; + getProps: (dateStats: DateStats) => Partial; + filterProps: (options: Partial) => Partial; + getDisplay: (dateStats: DateStats) => string; +} + +export const MONTH_RECURRENCE_INFO: RecurrenceInfo[] = [ + { + recurrenceType: MonthYearRecurrenceType.dayOfMonth, + hasProps: (options) => { + if (!options.bymonthday) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday > 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.monthDay], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber( + dateStats.monthDay + )} day of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bymonthday) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday < 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.daysUntilEndMonth * -1], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndMonth + )} day of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInMonth], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ + dateStats.dayName + } of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromMonthEnd * -1], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.weekdaysFromMonthEnd + )} ${dateStats.dayName} of the month`, + }, +]; + +const YEAR_RECURRENCE_INFO: RecurrenceInfo[] = [ + { + recurrenceType: MonthYearRecurrenceType.dayOfMonth, + hasProps: (options) => { + if (!options.bymonthday || !options.bymonth) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday > 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.monthDay], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on ${dateStats.monthName} ${formatOrdinalNumber( + dateStats.monthDay + )}`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bymonthday || !options.bymonth) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday < 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.daysUntilEndMonth * -1], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndMonth + )} day of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday || !options.bymonth) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInMonth], + byweekday: [dateStats.weekday], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ + dateStats.dayName + } of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday || !options.bymonth) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromMonthEnd * -1], + byweekday: [dateStats.weekday], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.weekdaysFromMonthEnd + )} ${dateStats.dayName} of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayOfYear, + hasProps: (options) => { + if (!options.byyearday) { + return false; + } + + const byyearday = Array.isArray(options.byyearday) + ? options.byyearday[0] + : options.byyearday; + + return byyearday > 0; + }, + getProps: (dateStats) => ({ + byyearday: [dateStats.yearDay], + }), + filterProps: (options) => ({ + byyearday: options.byyearday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.yearDay)} day of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfYear, + hasProps: (options) => { + if (!options.byyearday) { + return false; + } + + const byyearday = Array.isArray(options.byyearday) + ? options.byyearday[0] + : options.byyearday; + + return byyearday < 0; + }, + getProps: (dateStats) => ({ + byyearday: [dateStats.daysUntilEndYear * -1], + }), + filterProps: (options) => ({ + byyearday: options.byyearday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndYear + )} day of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInYear, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInYear], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInYear)} ${ + dateStats.dayName + } of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfYear, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromYearEnd * -1], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber(dateStats.weekdaysFromYearEnd)} ${ + dateStats.dayName + } of the year`, + }, +]; + +const RECURRENCE_INFO_MAP: { [key: number]: RecurrenceInfo[] } = { + [RRule.MONTHLY]: MONTH_RECURRENCE_INFO, + [RRule.YEARLY]: YEAR_RECURRENCE_INFO, +}; + +const MonthYearSelect = ({ + startDate, + options, + onChange, +}: MonthSelectProps) => { + const dateStats = getDateStats(startDate); + + const recurrenceInfo = RECURRENCE_INFO_MAP[options.freq ?? RRule.MONTHLY]; + + const currentRecurrenceInfo = + recurrenceInfo.find((info) => info.hasProps(options)) ?? + recurrenceInfo[0]; + + return ( +

+ +

+ ); +}; diff --git a/src/ui/overrides.css b/src/ui/overrides.css index 6e43fd58..5c86ef8c 100644 --- a/src/ui/overrides.css +++ b/src/ui/overrides.css @@ -80,3 +80,11 @@ border-color: black !important; border-width: 1px !important; } + +.fc-edit-control { + margin: 0 8px; +} + +.fc-recurring-control { + margin-left: 12px; +} From 91c8a1317043180ebf718fc1e4181c4dc1d70808 Mon Sep 17 00:00:00 2001 From: Ben Skeen Date: Wed, 13 Sep 2023 12:43:34 -0600 Subject: [PATCH 2/9] I added some more tests to cover the different recurrence rules. --- jest.config.js | 3 - package.json | 2 - src/ui/components/EditEventRecurrence.test.ts | 691 ----- src/ui/components/EditEventRecurrence.tsx | 400 +-- .../components/event-recurrence-types.test.ts | 2461 +++++++++++++++++ src/ui/components/event-recurrence-types.ts | 402 +++ 6 files changed, 2864 insertions(+), 1095 deletions(-) delete mode 100644 src/ui/components/EditEventRecurrence.test.ts create mode 100644 src/ui/components/event-recurrence-types.test.ts create mode 100644 src/ui/components/event-recurrence-types.ts diff --git a/jest.config.js b/jest.config.js index 02310684..34a9dd82 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,4 @@ module.exports = { preset: "ts-jest", testEnvironment: "node", - transform: { - "^.+\\.tsx?$": "ts-jest" - } }; diff --git a/package.json b/package.json index dc72d5b9..3206021f 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@types/node": "^16.11.6", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", - "@types/react-test-renderer": "^18.0.1", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "builtin-modules": "^3.2.0", @@ -41,7 +40,6 @@ "jest": "^29.3.1", "obsidian": "^0.16.3", "prettier": "^2.5.1", - "react-test-renderer": "^17.0.2", "ts-jest": "^29.0.3", "tslib": "2.3.1", "typescript": "4.4.4", diff --git a/src/ui/components/EditEventRecurrence.test.ts b/src/ui/components/EditEventRecurrence.test.ts deleted file mode 100644 index 911a4e65..00000000 --- a/src/ui/components/EditEventRecurrence.test.ts +++ /dev/null @@ -1,691 +0,0 @@ -import { Options, RRule } from "rrule"; -import { - DateStats, - MONTH_RECURRENCE_INFO, - MonthYearRecurrenceType, - RecurrenceInfo, - getDateStats, -} from "./EditEventRecurrence"; -import { DateTime } from "luxon"; - -describe("Recurrence Info", () => { - let currentInfo: RecurrenceInfo; - let options: Partial = { - freq: RRule.DAILY, - dtstart: new Date(), - interval: 1, - wkst: RRule.SU, - count: 1, - until: new Date(), - tzid: "UTC", - bysetpos: [1], - bymonth: [1], - bymonthday: [2], - bynmonthday: [3], - byyearday: [20], - byweekno: [1], - byweekday: [RRule.WE], - bynweekday: [[1]], - byhour: [2], - byminute: [42], - bysecond: [21], - byeaster: 1, - }; - - let dateStats: DateStats; - - let hasPropsResult: boolean; - let optionsResult: Partial; - let displayResult: string; - - const getRecurrenceInfo = (type: MonthYearRecurrenceType) => { - currentInfo = - MONTH_RECURRENCE_INFO.find( - (info) => info.recurrenceType === type - ) ?? MONTH_RECURRENCE_INFO[0]; - }; - - const actHasProps = () => { - hasPropsResult = currentInfo.hasProps(options); - }; - - const actGetProps = () => { - optionsResult = currentInfo.getProps(dateStats); - }; - - const actFilterProps = () => { - optionsResult = currentInfo.filterProps(options); - }; - - const actGetDisplay = () => { - displayResult = currentInfo.getDisplay(dateStats); - }; - - describe("MONTH_RECURRENCE_INFO", () => { - describe("dayOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayOfMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - bymonthday: [1], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - bymonthday: 1, - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = {}; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - bymonthday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats month day in the options", () => { - expect(optionsResult.bymonthday).toMatchObject([1]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return the bymonthday option in the options", () => { - expect(Object.keys(optionsResult)).toMatchObject([ - "bymonthday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 12th day of the month" - ); - }); - }); - }); - }); - - describe("dayBeforeEndOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - bymonthday: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - bymonthday: 1, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = {}; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - bymonthday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats daysBeforeEnd in the options", () => { - expect(optionsResult.bymonthday).toMatchObject([-30]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return the bymonthday option in the options", () => { - expect(Object.keys(optionsResult)).toMatchObject([ - "bymonthday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 19th to last day of the month" - ); - }); - }); - }); - }); - - describe("dayBeforeEndOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.weekdayInMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdayInMonth in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdayInMonth, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return two keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 2nd Tuesday of the month" - ); - }); - }); - }); - }); - - describe("dayBeforeEndOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo( - MonthYearRecurrenceType.weekdayBeforeEndOfMonth - ); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdaysFromMonthEnd in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdaysFromMonthEnd * -1, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return two keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 3rd to last Tuesday of the month" - ); - }); - }); - }); - }); - }); -}); diff --git a/src/ui/components/EditEventRecurrence.tsx b/src/ui/components/EditEventRecurrence.tsx index c48ebc15..e58399ea 100644 --- a/src/ui/components/EditEventRecurrence.tsx +++ b/src/ui/components/EditEventRecurrence.tsx @@ -2,6 +2,7 @@ import { RRule, Weekday, datetime, ByWeekday, Options } from "rrule"; import * as React from "react"; import { useCallback, useState } from "react"; import { DateTime } from "luxon"; +import { RECURRENCE_INFO_MAP, getDateStats } from "./event-recurrence-types"; interface EditEventRecurrenceProps { recurrence?: RRule; @@ -427,405 +428,6 @@ interface MonthSelectProps { onChange: (options: Partial) => void; } -export enum MonthYearRecurrenceType { - dayOfMonth = 0, - dayBeforeEndOfMonth = 1, - weekdayInMonth = 2, - weekdayBeforeEndOfMonth = 3, - dayOfYear = 4, - dayBeforeEndOfYear = 5, - weekdayInYear = 6, - weekdayBeforeEndOfYear = 7, -} - -export interface DateStats { - monthDay: number; - weekday: number; - daysUntilEndMonth: number; - weekdayInMonth: number; - weekdaysFromMonthEnd: number; - dayName: string; - month: number; - monthName: string; - yearDay: number; - daysUntilEndYear: number; - weekdayInYear: number; - weekdaysFromYearEnd: number; -} - -export const getDateStats = (date: DateTime): DateStats => { - const monthDay = Number(date.day); - const daysInMonth = date.daysInMonth; - const yearDay = date.ordinal; - const daysInYear = date.daysInYear; - - return { - monthDay, - weekday: date.weekday - 1, - daysUntilEndMonth: daysInMonth - monthDay + 1, - weekdayInMonth: Math.floor((monthDay - 1) / 7) + 1, - weekdaysFromMonthEnd: Math.floor((daysInMonth - monthDay) / 7) + 1, - dayName: date.weekdayLong, - month: date.month, - monthName: date.monthLong, - yearDay, - daysUntilEndYear: daysInYear - yearDay + 1, - weekdayInYear: Math.floor((yearDay - 1) / 7) + 1, - weekdaysFromYearEnd: Math.floor((daysInYear - yearDay) / 7) + 1, - }; -}; - -const formatOrdinalNumber = (value: number): string => { - const isBetween10And20 = value > 10 && value < 20; - const onesDigit = value % 10; - if (onesDigit === 1 && !isBetween10And20) { - return `${value}st`; - } - - if (onesDigit === 2 && !isBetween10And20) { - return `${value}nd`; - } - - if (onesDigit === 3 && !isBetween10And20) { - return `${value}rd`; - } - - return `${value}th`; -}; - -const formatLastOrdinalNumber = (value: number): string => { - if (value === 1) { - return "last"; - } - - return `${formatOrdinalNumber(value)} to last`; -}; - -export interface RecurrenceInfo { - recurrenceType: MonthYearRecurrenceType; - hasProps: (options: Partial) => boolean; - getProps: (dateStats: DateStats) => Partial; - filterProps: (options: Partial) => Partial; - getDisplay: (dateStats: DateStats) => string; -} - -export const MONTH_RECURRENCE_INFO: RecurrenceInfo[] = [ - { - recurrenceType: MonthYearRecurrenceType.dayOfMonth, - hasProps: (options) => { - if (!options.bymonthday) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday > 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.monthDay], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber( - dateStats.monthDay - )} day of the month`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bymonthday) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday < 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.daysUntilEndMonth * -1], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.daysUntilEndMonth - )} day of the month`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayInMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos > 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdayInMonth], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ - dateStats.dayName - } of the month`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos < 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdaysFromMonthEnd * -1], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.weekdaysFromMonthEnd - )} ${dateStats.dayName} of the month`, - }, -]; - -const YEAR_RECURRENCE_INFO: RecurrenceInfo[] = [ - { - recurrenceType: MonthYearRecurrenceType.dayOfMonth, - hasProps: (options) => { - if (!options.bymonthday || !options.bymonth) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday > 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.monthDay], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on ${dateStats.monthName} ${formatOrdinalNumber( - dateStats.monthDay - )}`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bymonthday || !options.bymonth) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday < 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.daysUntilEndMonth * -1], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.daysUntilEndMonth - )} day of ${dateStats.monthName}`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayInMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday || !options.bymonth) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos > 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdayInMonth], - byweekday: [dateStats.weekday], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ - dateStats.dayName - } of ${dateStats.monthName}`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday || !options.bymonth) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos < 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdaysFromMonthEnd * -1], - byweekday: [dateStats.weekday], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.weekdaysFromMonthEnd - )} ${dateStats.dayName} of ${dateStats.monthName}`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayOfYear, - hasProps: (options) => { - if (!options.byyearday) { - return false; - } - - const byyearday = Array.isArray(options.byyearday) - ? options.byyearday[0] - : options.byyearday; - - return byyearday > 0; - }, - getProps: (dateStats) => ({ - byyearday: [dateStats.yearDay], - }), - filterProps: (options) => ({ - byyearday: options.byyearday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.yearDay)} day of the year`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfYear, - hasProps: (options) => { - if (!options.byyearday) { - return false; - } - - const byyearday = Array.isArray(options.byyearday) - ? options.byyearday[0] - : options.byyearday; - - return byyearday < 0; - }, - getProps: (dateStats) => ({ - byyearday: [dateStats.daysUntilEndYear * -1], - }), - filterProps: (options) => ({ - byyearday: options.byyearday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.daysUntilEndYear - )} day of the year`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayInYear, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos > 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdayInYear], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.weekdayInYear)} ${ - dateStats.dayName - } of the year`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfYear, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos < 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdaysFromYearEnd * -1], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber(dateStats.weekdaysFromYearEnd)} ${ - dateStats.dayName - } of the year`, - }, -]; - -const RECURRENCE_INFO_MAP: { [key: number]: RecurrenceInfo[] } = { - [RRule.MONTHLY]: MONTH_RECURRENCE_INFO, - [RRule.YEARLY]: YEAR_RECURRENCE_INFO, -}; - const MonthYearSelect = ({ startDate, options, diff --git a/src/ui/components/event-recurrence-types.test.ts b/src/ui/components/event-recurrence-types.test.ts new file mode 100644 index 00000000..30576390 --- /dev/null +++ b/src/ui/components/event-recurrence-types.test.ts @@ -0,0 +1,2461 @@ +import { Options, RRule } from "rrule"; +import { DateTime } from "luxon"; +import { DateStats, MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, RecurrenceInfo, YEAR_RECURRENCE_INFO, formatOrdinalNumber, getDateStats } from "./event-recurrence-types"; + +describe("Recurrence Info", () => { + let currentInfo: RecurrenceInfo; + let options: Partial = { + freq: RRule.DAILY, + dtstart: new Date(), + interval: 1, + wkst: RRule.SU, + count: 1, + until: new Date(), + tzid: "UTC", + bysetpos: [1], + bymonth: [1], + bymonthday: [2], + bynmonthday: [3], + byyearday: [20], + byweekno: [1], + byweekday: [RRule.WE], + bynweekday: [[1]], + byhour: [2], + byminute: [42], + bysecond: [21], + byeaster: 1, + }; + + let dateStats: DateStats; + + let hasPropsResult: boolean; + let optionsResult: Partial; + let displayResult: string; + + let getRecurrenceInfo: (type: MonthYearRecurrenceType) => void; + + const actHasProps = () => { + hasPropsResult = currentInfo.hasProps(options); + }; + + const actGetProps = () => { + optionsResult = currentInfo.getProps(dateStats); + }; + + const actFilterProps = () => { + optionsResult = currentInfo.filterProps(options); + }; + + const actGetDisplay = () => { + displayResult = currentInfo.getDisplay(dateStats); + }; + + describe("MONTH_RECURRENCE_INFO", () => { + beforeEach(() => { + getRecurrenceInfo = (type) => { + currentInfo = + MONTH_RECURRENCE_INFO.find( + (info) => info.recurrenceType === type + ) ?? MONTH_RECURRENCE_INFO[0]; + }; + }); + + describe("dayOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayOfMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + bymonthday: [1], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + bymonthday: [-1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + bymonthday: 1, + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = {}; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + bymonthday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats month day in the options", () => { + expect(optionsResult.bymonthday).toMatchObject([1]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return the bymonthday option in the options", () => { + expect(Object.keys(optionsResult)).toMatchObject([ + "bymonthday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 12th day of the month" + ); + }); + }); + }); + }); + + describe("dayBeforeEndOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + bymonthday: [1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + bymonthday: [-1], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + bymonthday: 1, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = {}; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + bymonthday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats daysBeforeEnd in the options", () => { + expect(optionsResult.bymonthday).toMatchObject([-30]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return the bymonthday option in the options", () => { + expect(Object.keys(optionsResult)).toMatchObject([ + "bymonthday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 19th to last day of the month" + ); + }); + }); + }); + }); + + describe("weekdayInMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.weekdayInMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdayInMonth in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdayInMonth, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return two keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 2nd Tuesday of the month" + ); + }); + }); + }); + }); + + describe("weekdayBeforeEndOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo( + MonthYearRecurrenceType.weekdayBeforeEndOfMonth + ); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null, + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdaysFromMonthEnd in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdaysFromMonthEnd * -1, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return two keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 3rd to last Tuesday of the month" + ); + }); + }); + }); + }); + }); + + describe("YEAR_RECURRENCE_INFO", () => { + beforeEach(() => { + getRecurrenceInfo = (type) => { + currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => info.recurrenceType === type + ) ?? YEAR_RECURRENCE_INFO[0]; + }; + }); + + describe("dayOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayOfMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + bymonthday: [1], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + bymonthday: [-1], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + bymonthday: 1, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = { + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + bymonthday: null, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is undefined", () => { + beforeEach(() => { + options = { + bymonthday: [1] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is null", () => { + beforeEach(() => { + options = { + bymonthday: [1], + bymonth: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats month day in the options", () => { + expect(optionsResult.bymonthday).toMatchObject([1]); + }); + + it("should put the dateStats month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([9]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should return an options with two properties", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return an options with bymonthday", () => { + expect(Object.keys(optionsResult)).toContain("bymonthday"); + }); + + it("should return an options with bymonth", () => { + expect(Object.keys(optionsResult)).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on September 12th" + ); + }); + }); + }); + }); + + describe("dayBeforeEndOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + bymonthday: [1], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + bymonthday: [-1], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + bymonthday: 1, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = { + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + bymonthday: null, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is undefined", () => { + beforeEach(() => { + options = { + bymonthday: [-1] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is null", () => { + beforeEach(() => { + options = { + bymonthday: [-1], + bymonth: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats days until end of month in the options", () => { + expect(optionsResult.bymonthday).toMatchObject([-30]); + }); + + it("should put the dateStats month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([9]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should return an options with two properties", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return an options with bymonthday", () => { + expect(Object.keys(optionsResult)).toContain("bymonthday"); + }); + + it("should return an options with bymonth", () => { + expect(Object.keys(optionsResult)).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 19th to last day of September" + ); + }); + }); + }); + }); + + describe("weekdayInMonth", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.weekdayInMonth); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdayInMonth in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdayInMonth, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + + it("should put the month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([ + dateStats.month + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return three keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(3); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + + it("should return options that contain bymonth", () => { + expect(Object.keys(optionsResult)).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 2nd Tuesday of September" + ); + }); + }); + }); + }); + + describe("weekdayBeforeEndOfMonth", () => { + beforeEach(() => { + getRecurrenceInfo( + MonthYearRecurrenceType.weekdayBeforeEndOfMonth + ); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null, + bymonth: [9] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when month is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdaysFromMonthEnd in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdaysFromMonthEnd * -1, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + + it("should put the month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([ + dateStats.month + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return three keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(3); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + + it("should return options that contain bymonth", () => { + expect(Object.keys(optionsResult)).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 3rd to last Tuesday of September" + ); + }); + }); + }); + }); + + describe("dayOfYear", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayOfYear); + dateStats = getDateStats(DateTime.fromISO("2023-01-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + byyearday: [1] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + byyearday: [-1] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + byyearday: 1 + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + byyearday: -1 + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = {}; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + byyearday: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats year day in the options", () => { + expect(optionsResult.byyearday).toMatchObject([1]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should return an options with the expected property", () => { + expect(Object.keys(optionsResult)).toMatchObject(["byyearday"]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2024-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 256th day of the year" + ); + }); + }); + }); + }); + + describe("dayBeforeEndOfYear", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfYear); + dateStats = getDateStats(DateTime.fromISO("2024-01-01")); + }); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + beforeEach(() => { + options = { + byyearday: [1] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + beforeEach(() => { + options = { + byyearday: [-1] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + beforeEach(() => { + options = { + byyearday: 1 + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + beforeEach(() => { + options = { + byyearday: -1 + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when day is undefined", () => { + beforeEach(() => { + options = {}; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when day is null", () => { + beforeEach(() => { + options = { + byyearday: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the dateStats days until end of year in the options", () => { + expect(optionsResult.byyearday).toMatchObject([-366]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should return an options with the expected property", () => { + expect(Object.keys(optionsResult)).toMatchObject(["byyearday"]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2024-01-01") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 366th to last day of the year" + ); + }); + }); + }); + }); + + describe("weekdayInYear", () => { + beforeEach(() => { + getRecurrenceInfo(MonthYearRecurrenceType.weekdayInYear); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bymonthday: -1, + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdayInYear in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdayInYear, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return two keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 37th Tuesday of the year" + ); + }); + }); + }); + }); + + describe("weekdayBeforeEndOfYear", () => { + beforeEach(() => { + getRecurrenceInfo( + MonthYearRecurrenceType.weekdayBeforeEndOfYear + ); + dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + }); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + beforeEach(() => { + options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + beforeEach(() => { + options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + beforeEach(() => { + options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return true", () => { + expect(hasPropsResult).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + beforeEach(() => { + options = { + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + beforeEach(() => { + options = { + bysetpos: null, + byweekday: [RRule.MO.weekday] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + beforeEach(() => { + options = { + bysetpos: [1] + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + + describe("when weekday is null", () => { + beforeEach(() => { + options = { + bysetpos: [1], + byweekday: null + }; + + actHasProps(); + }); + + it("should return false", () => { + expect(hasPropsResult).toBe(false); + }); + }); + }); + + describe("getProps", () => { + beforeEach(() => { + actGetProps(); + }); + + it("should put the weekdaysFromYearEnd in the options", () => { + expect(optionsResult.bysetpos).toMatchObject([ + dateStats.weekdaysFromYearEnd * -1, + ]); + }); + + it("should put the weekday in the options", () => { + expect(optionsResult.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + beforeEach(() => { + actFilterProps(); + }); + + it("should only return two keys in the options", () => { + expect(Object.keys(optionsResult)).toHaveLength(2); + }); + + it("should return options that contain bysetpos", () => { + expect(Object.keys(optionsResult)).toContain("bysetpos"); + }); + + it("should return options that contain byweekday", () => { + expect(Object.keys(optionsResult)).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + beforeEach(() => { + dateStats = getDateStats( + DateTime.fromISO("2024-01-01") + ); + + actGetDisplay(); + }); + + it("should return the expected text", () => { + expect(displayResult).toBe( + "on the 53rd to last Monday of the year" + ); + }); + }); + }); + }); + }); +}); + +describe("formatOrdinalNumber", () => { + let value: number; + let result: string; + + const act = () => { + result = formatOrdinalNumber(value); + }; + + describe("when value ends in 1", () => { + describe("when the tens place is zero", () => { + describe("when the value is 1", () => { + beforeEach(() => { + value = 1; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("1st"); + }); + }); + + describe("when the value is 101", () => { + beforeEach(() => { + value = 101; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("101st"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 11", () => { + beforeEach(() => { + value = 11; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("11th"); + }); + }); + + describe("when the value is 111", () => { + beforeEach(() => { + value = 111; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("111th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 21", () => { + beforeEach(() => { + value = 21; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("21st"); + }); + }); + + describe("when the value is 121", () => { + beforeEach(() => { + value = 121; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("121st"); + }); + }); + }); + }); + + describe("when value ends in 2", () => { + describe("when the tens place is zero", () => { + describe("when the value is 2", () => { + beforeEach(() => { + value = 2; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("2nd"); + }); + }); + + describe("when the value is 102", () => { + beforeEach(() => { + value = 102; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("102nd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 12", () => { + beforeEach(() => { + value = 12; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("12th"); + }); + }); + + describe("when the value is 112", () => { + beforeEach(() => { + value = 112; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("112th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 22", () => { + beforeEach(() => { + value = 22; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("22nd"); + }); + }); + + describe("when the value is 122", () => { + beforeEach(() => { + value = 122; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("122nd"); + }); + }); + }); + }); + + describe("when value ends in 3", () => { + describe("when the tens place is zero", () => { + describe("when the value is 3", () => { + beforeEach(() => { + value = 3; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("3rd"); + }); + }); + + describe("when the value is 103", () => { + beforeEach(() => { + value = 103; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("103rd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 13", () => { + beforeEach(() => { + value = 13; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("13th"); + }); + }); + + describe("when the value is 113", () => { + beforeEach(() => { + value = 113; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("113th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 23", () => { + beforeEach(() => { + value = 23; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("23rd"); + }); + }); + + describe("when the value is 123", () => { + beforeEach(() => { + value = 123; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("123rd"); + }); + }); + }); + }); + + describe("when the value does not end in 1, 2 or 3", () => { + describe("when the value is 7", () => { + beforeEach(() => { + value = 7; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("7th"); + }); + }); + + describe("when the value is 17", () => { + beforeEach(() => { + value = 17; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("17th"); + }); + }); + + describe("when the value is 27", () => { + beforeEach(() => { + value = 27; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("27th"); + }); + }); + + describe("when the value is 107", () => { + beforeEach(() => { + value = 107; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("107th"); + }); + }); + + describe("when the value is 117", () => { + beforeEach(() => { + value = 117; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("117th"); + }); + }); + + describe("when the value is 127", () => { + beforeEach(() => { + value = 127; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("127th"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-types.ts b/src/ui/components/event-recurrence-types.ts new file mode 100644 index 00000000..65d09bab --- /dev/null +++ b/src/ui/components/event-recurrence-types.ts @@ -0,0 +1,402 @@ +import { DateTime } from "luxon"; +import { Options, RRule } from "rrule"; + +export enum MonthYearRecurrenceType { + dayOfMonth = 0, + dayBeforeEndOfMonth = 1, + weekdayInMonth = 2, + weekdayBeforeEndOfMonth = 3, + dayOfYear = 4, + dayBeforeEndOfYear = 5, + weekdayInYear = 6, + weekdayBeforeEndOfYear = 7, +} + +export interface DateStats { + monthDay: number; + weekday: number; + daysUntilEndMonth: number; + weekdayInMonth: number; + weekdaysFromMonthEnd: number; + dayName: string; + month: number; + monthName: string; + yearDay: number; + daysUntilEndYear: number; + weekdayInYear: number; + weekdaysFromYearEnd: number; +} + +export const getDateStats = (date: DateTime): DateStats => { + const monthDay = Number(date.day); + const daysInMonth = date.daysInMonth; + const yearDay = date.ordinal; + const daysInYear = date.daysInYear; + + return { + monthDay, + weekday: date.weekday - 1, + daysUntilEndMonth: daysInMonth - monthDay + 1, + weekdayInMonth: Math.floor((monthDay - 1) / 7) + 1, + weekdaysFromMonthEnd: Math.floor((daysInMonth - monthDay) / 7) + 1, + dayName: date.weekdayLong, + month: date.month, + monthName: date.monthLong, + yearDay, + daysUntilEndYear: daysInYear - yearDay + 1, + weekdayInYear: Math.floor((yearDay - 1) / 7) + 1, + weekdaysFromYearEnd: Math.floor((daysInYear - yearDay) / 7) + 1, + }; +}; + +export const formatOrdinalNumber = (value: number): string => { + const tensValue = value % 100; + const isBetween10And20 = tensValue > 10 && tensValue < 20; + const onesDigit = value % 10; + if (onesDigit === 1 && !isBetween10And20) { + return `${value}st`; + } + + if (onesDigit === 2 && !isBetween10And20) { + return `${value}nd`; + } + + if (onesDigit === 3 && !isBetween10And20) { + return `${value}rd`; + } + + return `${value}th`; +}; + +export const formatLastOrdinalNumber = (value: number): string => { + if (value === 1) { + return "last"; + } + + return `${formatOrdinalNumber(value)} to last`; +}; + +export interface RecurrenceInfo { + recurrenceType: MonthYearRecurrenceType; + hasProps: (options: Partial) => boolean; + getProps: (dateStats: DateStats) => Partial; + filterProps: (options: Partial) => Partial; + getDisplay: (dateStats: DateStats) => string; +} + +export const MONTH_RECURRENCE_INFO: RecurrenceInfo[] = [ + { + recurrenceType: MonthYearRecurrenceType.dayOfMonth, + hasProps: (options) => { + if (!options.bymonthday) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday > 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.monthDay], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber( + dateStats.monthDay + )} day of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bymonthday) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday < 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.daysUntilEndMonth * -1], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndMonth + )} day of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInMonth], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ + dateStats.dayName + } of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromMonthEnd * -1], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.weekdaysFromMonthEnd + )} ${dateStats.dayName} of the month`, + }, +]; + +export const YEAR_RECURRENCE_INFO: RecurrenceInfo[] = [ + { + recurrenceType: MonthYearRecurrenceType.dayOfMonth, + hasProps: (options) => { + if (!options.bymonthday || !options.bymonth) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday > 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.monthDay], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on ${dateStats.monthName} ${formatOrdinalNumber( + dateStats.monthDay + )}`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bymonthday || !options.bymonth) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday < 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.daysUntilEndMonth * -1], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndMonth + )} day of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday || !options.bymonth) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInMonth], + byweekday: [dateStats.weekday], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ + dateStats.dayName + } of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday || !options.bymonth) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromMonthEnd * -1], + byweekday: [dateStats.weekday], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.weekdaysFromMonthEnd + )} ${dateStats.dayName} of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayOfYear, + hasProps: (options) => { + if (!options.byyearday) { + return false; + } + + const byyearday = Array.isArray(options.byyearday) + ? options.byyearday[0] + : options.byyearday; + + return byyearday > 0; + }, + getProps: (dateStats) => ({ + byyearday: [dateStats.yearDay], + }), + filterProps: (options) => ({ + byyearday: options.byyearday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.yearDay)} day of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfYear, + hasProps: (options) => { + if (!options.byyearday) { + return false; + } + + const byyearday = Array.isArray(options.byyearday) + ? options.byyearday[0] + : options.byyearday; + + return byyearday < 0; + }, + getProps: (dateStats) => ({ + byyearday: [dateStats.daysUntilEndYear * -1], + }), + filterProps: (options) => ({ + byyearday: options.byyearday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndYear + )} day of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInYear, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInYear], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInYear)} ${ + dateStats.dayName + } of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfYear, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromYearEnd * -1], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber(dateStats.weekdaysFromYearEnd)} ${ + dateStats.dayName + } of the year`, + }, +]; + +export const RECURRENCE_INFO_MAP: { [key: number]: RecurrenceInfo[] } = { + [RRule.MONTHLY]: MONTH_RECURRENCE_INFO, + [RRule.YEARLY]: YEAR_RECURRENCE_INFO, +}; From 77cd2b0a8d332b6105451b0b1a9f0980c5cade2e Mon Sep 17 00:00:00 2001 From: Ben Skeen Date: Wed, 13 Sep 2023 12:48:55 -0600 Subject: [PATCH 3/9] I removed quotes from the package.json lint commands to make those work cross-platform. --- package.json | 4 +- .../components/event-recurrence-types.test.ts | 882 +++++++++--------- src/ui/components/event-recurrence-types.ts | 804 ++++++++-------- 3 files changed, 850 insertions(+), 840 deletions(-) diff --git a/package.json b/package.json index 3206021f..8dc05243 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "dev": "node esbuild.config.mjs", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "version": "node version-bump.mjs && git add manifest.json versions.json", - "lint": "prettier --check 'src/**/*.ts*'", - "fix-lint": "prettier --write 'src/**/*.ts*'", + "lint": "prettier --check ./src/**/*.ts*", + "fix-lint": "prettier --write ./src/**/*.ts*", "compile": "tsc -noEmit --skipLibCheck", "prepare": "husky install", "test": "jest --ci --silent=true", diff --git a/src/ui/components/event-recurrence-types.test.ts b/src/ui/components/event-recurrence-types.test.ts index 30576390..6c259a46 100644 --- a/src/ui/components/event-recurrence-types.test.ts +++ b/src/ui/components/event-recurrence-types.test.ts @@ -1,6 +1,14 @@ import { Options, RRule } from "rrule"; import { DateTime } from "luxon"; -import { DateStats, MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, RecurrenceInfo, YEAR_RECURRENCE_INFO, formatOrdinalNumber, getDateStats } from "./event-recurrence-types"; +import { + DateStats, + MONTH_RECURRENCE_INFO, + MonthYearRecurrenceType, + RecurrenceInfo, + YEAR_RECURRENCE_INFO, + formatOrdinalNumber, + getDateStats, +} from "./event-recurrence-types"; describe("Recurrence Info", () => { let currentInfo: RecurrenceInfo; @@ -51,14 +59,14 @@ describe("Recurrence Info", () => { }; describe("MONTH_RECURRENCE_INFO", () => { - beforeEach(() => { - getRecurrenceInfo = (type) => { - currentInfo = - MONTH_RECURRENCE_INFO.find( - (info) => info.recurrenceType === type - ) ?? MONTH_RECURRENCE_INFO[0]; - }; - }); + beforeEach(() => { + getRecurrenceInfo = (type) => { + currentInfo = + MONTH_RECURRENCE_INFO.find( + (info) => info.recurrenceType === type + ) ?? MONTH_RECURRENCE_INFO[0]; + }; + }); describe("dayOfMonth", () => { beforeEach(() => { @@ -688,14 +696,14 @@ describe("Recurrence Info", () => { }); describe("YEAR_RECURRENCE_INFO", () => { - beforeEach(() => { - getRecurrenceInfo = (type) => { - currentInfo = - YEAR_RECURRENCE_INFO.find( - (info) => info.recurrenceType === type - ) ?? YEAR_RECURRENCE_INFO[0]; - }; - }); + beforeEach(() => { + getRecurrenceInfo = (type) => { + currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => info.recurrenceType === type + ) ?? YEAR_RECURRENCE_INFO[0]; + }; + }); describe("dayOfMonth", () => { beforeEach(() => { @@ -708,7 +716,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: [1], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -723,7 +731,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: [-1], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -738,7 +746,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: 1, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -753,7 +761,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: -1, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -767,8 +775,8 @@ describe("Recurrence Info", () => { describe("when day is undefined", () => { beforeEach(() => { options = { - bymonth: [9] - }; + bymonth: [9], + }; actHasProps(); }); @@ -782,7 +790,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: null, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -796,8 +804,8 @@ describe("Recurrence Info", () => { describe("when month is undefined", () => { beforeEach(() => { options = { - bymonthday: [1] - }; + bymonthday: [1], + }; actHasProps(); }); @@ -811,7 +819,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: [1], - bymonth: null + bymonth: null, }; actHasProps(); @@ -832,9 +840,9 @@ describe("Recurrence Info", () => { expect(optionsResult.bymonthday).toMatchObject([1]); }); - it("should put the dateStats month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([9]); - }); + it("should put the dateStats month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([9]); + }); }); describe("filterProps", () => { @@ -846,13 +854,13 @@ describe("Recurrence Info", () => { expect(Object.keys(optionsResult)).toHaveLength(2); }); - it("should return an options with bymonthday", () => { - expect(Object.keys(optionsResult)).toContain("bymonthday"); - }); + it("should return an options with bymonthday", () => { + expect(Object.keys(optionsResult)).toContain("bymonthday"); + }); - it("should return an options with bymonth", () => { - expect(Object.keys(optionsResult)).toContain("bymonth"); - }); + it("should return an options with bymonth", () => { + expect(Object.keys(optionsResult)).toContain("bymonth"); + }); }); describe("getDisplay", () => { @@ -866,9 +874,7 @@ describe("Recurrence Info", () => { }); it("should return the expected text", () => { - expect(displayResult).toBe( - "on September 12th" - ); + expect(displayResult).toBe("on September 12th"); }); }); }); @@ -885,7 +891,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: [1], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -900,7 +906,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: [-1], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -915,7 +921,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: 1, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -930,7 +936,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: -1, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -944,8 +950,8 @@ describe("Recurrence Info", () => { describe("when day is undefined", () => { beforeEach(() => { options = { - bymonth: [9] - }; + bymonth: [9], + }; actHasProps(); }); @@ -959,7 +965,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: null, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -973,8 +979,8 @@ describe("Recurrence Info", () => { describe("when month is undefined", () => { beforeEach(() => { options = { - bymonthday: [-1] - }; + bymonthday: [-1], + }; actHasProps(); }); @@ -988,7 +994,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: [-1], - bymonth: null + bymonth: null, }; actHasProps(); @@ -1009,9 +1015,9 @@ describe("Recurrence Info", () => { expect(optionsResult.bymonthday).toMatchObject([-30]); }); - it("should put the dateStats month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([9]); - }); + it("should put the dateStats month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([9]); + }); }); describe("filterProps", () => { @@ -1023,13 +1029,13 @@ describe("Recurrence Info", () => { expect(Object.keys(optionsResult)).toHaveLength(2); }); - it("should return an options with bymonthday", () => { - expect(Object.keys(optionsResult)).toContain("bymonthday"); - }); + it("should return an options with bymonthday", () => { + expect(Object.keys(optionsResult)).toContain("bymonthday"); + }); - it("should return an options with bymonth", () => { - expect(Object.keys(optionsResult)).toContain("bymonth"); - }); + it("should return an options with bymonth", () => { + expect(Object.keys(optionsResult)).toContain("bymonth"); + }); }); describe("getDisplay", () => { @@ -1063,7 +1069,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [1], byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1079,7 +1085,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [-1], byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1095,7 +1101,7 @@ describe("Recurrence Info", () => { options = { bysetpos: 1, byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1111,7 +1117,7 @@ describe("Recurrence Info", () => { options = { bymonthday: -1, byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1126,7 +1132,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1142,7 +1148,7 @@ describe("Recurrence Info", () => { options = { bysetpos: null, byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1157,7 +1163,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1173,7 +1179,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [1], byweekday: null, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1188,7 +1194,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1204,7 +1210,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [1], byweekday: [RRule.MO.weekday], - bymonth: null + bymonth: null, }; actHasProps(); @@ -1233,11 +1239,11 @@ describe("Recurrence Info", () => { ]); }); - it("should put the month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([ - dateStats.month - ]); - }); + it("should put the month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([ + dateStats.month, + ]); + }); }); describe("filterProps", () => { @@ -1295,7 +1301,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [1], byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1311,7 +1317,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [-1], byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1327,7 +1333,7 @@ describe("Recurrence Info", () => { options = { bysetpos: 1, byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1343,7 +1349,7 @@ describe("Recurrence Info", () => { options = { bysetpos: -1, byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1358,7 +1364,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1374,7 +1380,7 @@ describe("Recurrence Info", () => { options = { bysetpos: null, byweekday: [RRule.MO.weekday], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1389,7 +1395,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1405,7 +1411,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [1], byweekday: null, - bymonth: [9] + bymonth: [9], }; actHasProps(); @@ -1420,7 +1426,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1436,7 +1442,7 @@ describe("Recurrence Info", () => { options = { bysetpos: [1], byweekday: [RRule.MO.weekday], - bymonth: null + bymonth: null, }; actHasProps(); @@ -1465,11 +1471,11 @@ describe("Recurrence Info", () => { ]); }); - it("should put the month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([ - dateStats.month - ]); - }); + it("should put the month in the options", () => { + expect(optionsResult.bymonth).toMatchObject([ + dateStats.month, + ]); + }); }); describe("filterProps", () => { @@ -1523,7 +1529,7 @@ describe("Recurrence Info", () => { describe("when day is an array with a positive number", () => { beforeEach(() => { options = { - byyearday: [1] + byyearday: [1], }; actHasProps(); @@ -1537,7 +1543,7 @@ describe("Recurrence Info", () => { describe("when day is an array with a negative number", () => { beforeEach(() => { options = { - byyearday: [-1] + byyearday: [-1], }; actHasProps(); @@ -1551,7 +1557,7 @@ describe("Recurrence Info", () => { describe("when day is a positive number", () => { beforeEach(() => { options = { - byyearday: 1 + byyearday: 1, }; actHasProps(); @@ -1565,7 +1571,7 @@ describe("Recurrence Info", () => { describe("when day is a negative number", () => { beforeEach(() => { options = { - byyearday: -1 + byyearday: -1, }; actHasProps(); @@ -1591,7 +1597,7 @@ describe("Recurrence Info", () => { describe("when day is null", () => { beforeEach(() => { options = { - byyearday: null + byyearday: null, }; actHasProps(); @@ -1619,7 +1625,9 @@ describe("Recurrence Info", () => { }); it("should return an options with the expected property", () => { - expect(Object.keys(optionsResult)).toMatchObject(["byyearday"]); + expect(Object.keys(optionsResult)).toMatchObject([ + "byyearday", + ]); }); }); @@ -1652,7 +1660,7 @@ describe("Recurrence Info", () => { describe("when day is an array with a positive number", () => { beforeEach(() => { options = { - byyearday: [1] + byyearday: [1], }; actHasProps(); @@ -1666,7 +1674,7 @@ describe("Recurrence Info", () => { describe("when day is an array with a negative number", () => { beforeEach(() => { options = { - byyearday: [-1] + byyearday: [-1], }; actHasProps(); @@ -1680,7 +1688,7 @@ describe("Recurrence Info", () => { describe("when day is a positive number", () => { beforeEach(() => { options = { - byyearday: 1 + byyearday: 1, }; actHasProps(); @@ -1694,7 +1702,7 @@ describe("Recurrence Info", () => { describe("when day is a negative number", () => { beforeEach(() => { options = { - byyearday: -1 + byyearday: -1, }; actHasProps(); @@ -1720,7 +1728,7 @@ describe("Recurrence Info", () => { describe("when day is null", () => { beforeEach(() => { options = { - byyearday: null + byyearday: null, }; actHasProps(); @@ -1748,7 +1756,9 @@ describe("Recurrence Info", () => { }); it("should return an options with the expected property", () => { - expect(Object.keys(optionsResult)).toMatchObject(["byyearday"]); + expect(Object.keys(optionsResult)).toMatchObject([ + "byyearday", + ]); }); }); @@ -1782,7 +1792,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1797,7 +1807,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [-1], - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1812,7 +1822,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: 1, - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1827,7 +1837,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bymonthday: -1, - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1841,7 +1851,7 @@ describe("Recurrence Info", () => { describe("when bysetpos is undefined", () => { beforeEach(() => { options = { - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1856,7 +1866,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: null, - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1870,7 +1880,7 @@ describe("Recurrence Info", () => { describe("when weekday is undefined", () => { beforeEach(() => { options = { - bysetpos: [1] + bysetpos: [1], }; actHasProps(); @@ -1885,7 +1895,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - byweekday: null + byweekday: null, }; actHasProps(); @@ -1965,7 +1975,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1980,7 +1990,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [-1], - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -1995,7 +2005,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: 1, - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -2010,7 +2020,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: -1, - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -2024,7 +2034,7 @@ describe("Recurrence Info", () => { describe("when bysetpos is undefined", () => { beforeEach(() => { options = { - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -2039,7 +2049,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: null, - byweekday: [RRule.MO.weekday] + byweekday: [RRule.MO.weekday], }; actHasProps(); @@ -2053,7 +2063,7 @@ describe("Recurrence Info", () => { describe("when weekday is undefined", () => { beforeEach(() => { options = { - bysetpos: [1] + bysetpos: [1], }; actHasProps(); @@ -2068,7 +2078,7 @@ describe("Recurrence Info", () => { beforeEach(() => { options = { bysetpos: [1], - byweekday: null + byweekday: null, }; actHasProps(); @@ -2138,324 +2148,324 @@ describe("Recurrence Info", () => { }); describe("formatOrdinalNumber", () => { - let value: number; - let result: string; - - const act = () => { - result = formatOrdinalNumber(value); - }; - - describe("when value ends in 1", () => { - describe("when the tens place is zero", () => { - describe("when the value is 1", () => { - beforeEach(() => { - value = 1; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("1st"); - }); - }); - - describe("when the value is 101", () => { - beforeEach(() => { - value = 101; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("101st"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 11", () => { - beforeEach(() => { - value = 11; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("11th"); - }); - }); - - describe("when the value is 111", () => { - beforeEach(() => { - value = 111; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("111th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 21", () => { - beforeEach(() => { - value = 21; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("21st"); - }); - }); - - describe("when the value is 121", () => { - beforeEach(() => { - value = 121; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("121st"); - }); - }); - }); - }); - - describe("when value ends in 2", () => { - describe("when the tens place is zero", () => { - describe("when the value is 2", () => { - beforeEach(() => { - value = 2; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("2nd"); - }); - }); - - describe("when the value is 102", () => { - beforeEach(() => { - value = 102; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("102nd"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 12", () => { - beforeEach(() => { - value = 12; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("12th"); - }); - }); - - describe("when the value is 112", () => { - beforeEach(() => { - value = 112; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("112th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 22", () => { - beforeEach(() => { - value = 22; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("22nd"); - }); - }); - - describe("when the value is 122", () => { - beforeEach(() => { - value = 122; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("122nd"); - }); - }); - }); - }); - - describe("when value ends in 3", () => { - describe("when the tens place is zero", () => { - describe("when the value is 3", () => { - beforeEach(() => { - value = 3; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("3rd"); - }); - }); - - describe("when the value is 103", () => { - beforeEach(() => { - value = 103; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("103rd"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 13", () => { - beforeEach(() => { - value = 13; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("13th"); - }); - }); - - describe("when the value is 113", () => { - beforeEach(() => { - value = 113; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("113th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 23", () => { - beforeEach(() => { - value = 23; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("23rd"); - }); - }); - - describe("when the value is 123", () => { - beforeEach(() => { - value = 123; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("123rd"); - }); - }); - }); - }); - - describe("when the value does not end in 1, 2 or 3", () => { - describe("when the value is 7", () => { - beforeEach(() => { - value = 7; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("7th"); - }); - }); - - describe("when the value is 17", () => { - beforeEach(() => { - value = 17; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("17th"); - }); - }); - - describe("when the value is 27", () => { - beforeEach(() => { - value = 27; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("27th"); - }); - }); - - describe("when the value is 107", () => { - beforeEach(() => { - value = 107; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("107th"); - }); - }); - - describe("when the value is 117", () => { - beforeEach(() => { - value = 117; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("117th"); - }); - }); - - describe("when the value is 127", () => { - beforeEach(() => { - value = 127; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("127th"); - }); - }); - }); + let value: number; + let result: string; + + const act = () => { + result = formatOrdinalNumber(value); + }; + + describe("when value ends in 1", () => { + describe("when the tens place is zero", () => { + describe("when the value is 1", () => { + beforeEach(() => { + value = 1; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("1st"); + }); + }); + + describe("when the value is 101", () => { + beforeEach(() => { + value = 101; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("101st"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 11", () => { + beforeEach(() => { + value = 11; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("11th"); + }); + }); + + describe("when the value is 111", () => { + beforeEach(() => { + value = 111; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("111th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 21", () => { + beforeEach(() => { + value = 21; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("21st"); + }); + }); + + describe("when the value is 121", () => { + beforeEach(() => { + value = 121; + + act(); + }); + + it("should end in st", () => { + expect(result).toBe("121st"); + }); + }); + }); + }); + + describe("when value ends in 2", () => { + describe("when the tens place is zero", () => { + describe("when the value is 2", () => { + beforeEach(() => { + value = 2; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("2nd"); + }); + }); + + describe("when the value is 102", () => { + beforeEach(() => { + value = 102; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("102nd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 12", () => { + beforeEach(() => { + value = 12; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("12th"); + }); + }); + + describe("when the value is 112", () => { + beforeEach(() => { + value = 112; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("112th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 22", () => { + beforeEach(() => { + value = 22; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("22nd"); + }); + }); + + describe("when the value is 122", () => { + beforeEach(() => { + value = 122; + + act(); + }); + + it("should end in nd", () => { + expect(result).toBe("122nd"); + }); + }); + }); + }); + + describe("when value ends in 3", () => { + describe("when the tens place is zero", () => { + describe("when the value is 3", () => { + beforeEach(() => { + value = 3; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("3rd"); + }); + }); + + describe("when the value is 103", () => { + beforeEach(() => { + value = 103; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("103rd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 13", () => { + beforeEach(() => { + value = 13; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("13th"); + }); + }); + + describe("when the value is 113", () => { + beforeEach(() => { + value = 113; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("113th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 23", () => { + beforeEach(() => { + value = 23; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("23rd"); + }); + }); + + describe("when the value is 123", () => { + beforeEach(() => { + value = 123; + + act(); + }); + + it("should end in rd", () => { + expect(result).toBe("123rd"); + }); + }); + }); + }); + + describe("when the value does not end in 1, 2 or 3", () => { + describe("when the value is 7", () => { + beforeEach(() => { + value = 7; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("7th"); + }); + }); + + describe("when the value is 17", () => { + beforeEach(() => { + value = 17; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("17th"); + }); + }); + + describe("when the value is 27", () => { + beforeEach(() => { + value = 27; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("27th"); + }); + }); + + describe("when the value is 107", () => { + beforeEach(() => { + value = 107; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("107th"); + }); + }); + + describe("when the value is 117", () => { + beforeEach(() => { + value = 117; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("117th"); + }); + }); + + describe("when the value is 127", () => { + beforeEach(() => { + value = 127; + + act(); + }); + + it("should end in th", () => { + expect(result).toBe("127th"); + }); + }); + }); }); diff --git a/src/ui/components/event-recurrence-types.ts b/src/ui/components/event-recurrence-types.ts index 65d09bab..75482f95 100644 --- a/src/ui/components/event-recurrence-types.ts +++ b/src/ui/components/event-recurrence-types.ts @@ -1,402 +1,402 @@ -import { DateTime } from "luxon"; -import { Options, RRule } from "rrule"; - -export enum MonthYearRecurrenceType { - dayOfMonth = 0, - dayBeforeEndOfMonth = 1, - weekdayInMonth = 2, - weekdayBeforeEndOfMonth = 3, - dayOfYear = 4, - dayBeforeEndOfYear = 5, - weekdayInYear = 6, - weekdayBeforeEndOfYear = 7, -} - -export interface DateStats { - monthDay: number; - weekday: number; - daysUntilEndMonth: number; - weekdayInMonth: number; - weekdaysFromMonthEnd: number; - dayName: string; - month: number; - monthName: string; - yearDay: number; - daysUntilEndYear: number; - weekdayInYear: number; - weekdaysFromYearEnd: number; -} - -export const getDateStats = (date: DateTime): DateStats => { - const monthDay = Number(date.day); - const daysInMonth = date.daysInMonth; - const yearDay = date.ordinal; - const daysInYear = date.daysInYear; - - return { - monthDay, - weekday: date.weekday - 1, - daysUntilEndMonth: daysInMonth - monthDay + 1, - weekdayInMonth: Math.floor((monthDay - 1) / 7) + 1, - weekdaysFromMonthEnd: Math.floor((daysInMonth - monthDay) / 7) + 1, - dayName: date.weekdayLong, - month: date.month, - monthName: date.monthLong, - yearDay, - daysUntilEndYear: daysInYear - yearDay + 1, - weekdayInYear: Math.floor((yearDay - 1) / 7) + 1, - weekdaysFromYearEnd: Math.floor((daysInYear - yearDay) / 7) + 1, - }; -}; - -export const formatOrdinalNumber = (value: number): string => { - const tensValue = value % 100; - const isBetween10And20 = tensValue > 10 && tensValue < 20; - const onesDigit = value % 10; - if (onesDigit === 1 && !isBetween10And20) { - return `${value}st`; - } - - if (onesDigit === 2 && !isBetween10And20) { - return `${value}nd`; - } - - if (onesDigit === 3 && !isBetween10And20) { - return `${value}rd`; - } - - return `${value}th`; -}; - -export const formatLastOrdinalNumber = (value: number): string => { - if (value === 1) { - return "last"; - } - - return `${formatOrdinalNumber(value)} to last`; -}; - -export interface RecurrenceInfo { - recurrenceType: MonthYearRecurrenceType; - hasProps: (options: Partial) => boolean; - getProps: (dateStats: DateStats) => Partial; - filterProps: (options: Partial) => Partial; - getDisplay: (dateStats: DateStats) => string; -} - -export const MONTH_RECURRENCE_INFO: RecurrenceInfo[] = [ - { - recurrenceType: MonthYearRecurrenceType.dayOfMonth, - hasProps: (options) => { - if (!options.bymonthday) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday > 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.monthDay], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber( - dateStats.monthDay - )} day of the month`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bymonthday) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday < 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.daysUntilEndMonth * -1], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.daysUntilEndMonth - )} day of the month`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayInMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos > 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdayInMonth], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ - dateStats.dayName - } of the month`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos < 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdaysFromMonthEnd * -1], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.weekdaysFromMonthEnd - )} ${dateStats.dayName} of the month`, - }, -]; - -export const YEAR_RECURRENCE_INFO: RecurrenceInfo[] = [ - { - recurrenceType: MonthYearRecurrenceType.dayOfMonth, - hasProps: (options) => { - if (!options.bymonthday || !options.bymonth) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday > 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.monthDay], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on ${dateStats.monthName} ${formatOrdinalNumber( - dateStats.monthDay - )}`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bymonthday || !options.bymonth) { - return false; - } - - const bymonthday = Array.isArray(options.bymonthday) - ? options.bymonthday[0] - : options.bymonthday; - - return bymonthday < 0; - }, - getProps: (dateStats) => ({ - bymonthday: [dateStats.daysUntilEndMonth * -1], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bymonthday: options.bymonthday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.daysUntilEndMonth - )} day of ${dateStats.monthName}`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayInMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday || !options.bymonth) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos > 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdayInMonth], - byweekday: [dateStats.weekday], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ - dateStats.dayName - } of ${dateStats.monthName}`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday || !options.bymonth) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos < 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdaysFromMonthEnd * -1], - byweekday: [dateStats.weekday], - bymonth: [dateStats.month], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - bymonth: options.bymonth, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.weekdaysFromMonthEnd - )} ${dateStats.dayName} of ${dateStats.monthName}`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayOfYear, - hasProps: (options) => { - if (!options.byyearday) { - return false; - } - - const byyearday = Array.isArray(options.byyearday) - ? options.byyearday[0] - : options.byyearday; - - return byyearday > 0; - }, - getProps: (dateStats) => ({ - byyearday: [dateStats.yearDay], - }), - filterProps: (options) => ({ - byyearday: options.byyearday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.yearDay)} day of the year`, - }, - { - recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfYear, - hasProps: (options) => { - if (!options.byyearday) { - return false; - } - - const byyearday = Array.isArray(options.byyearday) - ? options.byyearday[0] - : options.byyearday; - - return byyearday < 0; - }, - getProps: (dateStats) => ({ - byyearday: [dateStats.daysUntilEndYear * -1], - }), - filterProps: (options) => ({ - byyearday: options.byyearday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber( - dateStats.daysUntilEndYear - )} day of the year`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayInYear, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos > 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdayInYear], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatOrdinalNumber(dateStats.weekdayInYear)} ${ - dateStats.dayName - } of the year`, - }, - { - recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfYear, - hasProps: (options) => { - if (!options.bysetpos || !options.byweekday) { - return false; - } - - const bysetpos = Array.isArray(options.bysetpos) - ? options.bysetpos[0] - : options.bysetpos; - - return bysetpos < 0; - }, - getProps: (dateStats) => ({ - bysetpos: [dateStats.weekdaysFromYearEnd * -1], - byweekday: [dateStats.weekday], - }), - filterProps: (options) => ({ - bysetpos: options.bysetpos, - byweekday: options.byweekday, - }), - getDisplay: (dateStats) => - `on the ${formatLastOrdinalNumber(dateStats.weekdaysFromYearEnd)} ${ - dateStats.dayName - } of the year`, - }, -]; - -export const RECURRENCE_INFO_MAP: { [key: number]: RecurrenceInfo[] } = { - [RRule.MONTHLY]: MONTH_RECURRENCE_INFO, - [RRule.YEARLY]: YEAR_RECURRENCE_INFO, -}; +import { DateTime } from "luxon"; +import { Options, RRule } from "rrule"; + +export enum MonthYearRecurrenceType { + dayOfMonth = 0, + dayBeforeEndOfMonth = 1, + weekdayInMonth = 2, + weekdayBeforeEndOfMonth = 3, + dayOfYear = 4, + dayBeforeEndOfYear = 5, + weekdayInYear = 6, + weekdayBeforeEndOfYear = 7, +} + +export interface DateStats { + monthDay: number; + weekday: number; + daysUntilEndMonth: number; + weekdayInMonth: number; + weekdaysFromMonthEnd: number; + dayName: string; + month: number; + monthName: string; + yearDay: number; + daysUntilEndYear: number; + weekdayInYear: number; + weekdaysFromYearEnd: number; +} + +export const getDateStats = (date: DateTime): DateStats => { + const monthDay = Number(date.day); + const daysInMonth = date.daysInMonth; + const yearDay = date.ordinal; + const daysInYear = date.daysInYear; + + return { + monthDay, + weekday: date.weekday - 1, + daysUntilEndMonth: daysInMonth - monthDay + 1, + weekdayInMonth: Math.floor((monthDay - 1) / 7) + 1, + weekdaysFromMonthEnd: Math.floor((daysInMonth - monthDay) / 7) + 1, + dayName: date.weekdayLong, + month: date.month, + monthName: date.monthLong, + yearDay, + daysUntilEndYear: daysInYear - yearDay + 1, + weekdayInYear: Math.floor((yearDay - 1) / 7) + 1, + weekdaysFromYearEnd: Math.floor((daysInYear - yearDay) / 7) + 1, + }; +}; + +export const formatOrdinalNumber = (value: number): string => { + const tensValue = value % 100; + const isBetween10And20 = tensValue > 10 && tensValue < 20; + const onesDigit = value % 10; + if (onesDigit === 1 && !isBetween10And20) { + return `${value}st`; + } + + if (onesDigit === 2 && !isBetween10And20) { + return `${value}nd`; + } + + if (onesDigit === 3 && !isBetween10And20) { + return `${value}rd`; + } + + return `${value}th`; +}; + +export const formatLastOrdinalNumber = (value: number): string => { + if (value === 1) { + return "last"; + } + + return `${formatOrdinalNumber(value)} to last`; +}; + +export interface RecurrenceInfo { + recurrenceType: MonthYearRecurrenceType; + hasProps: (options: Partial) => boolean; + getProps: (dateStats: DateStats) => Partial; + filterProps: (options: Partial) => Partial; + getDisplay: (dateStats: DateStats) => string; +} + +export const MONTH_RECURRENCE_INFO: RecurrenceInfo[] = [ + { + recurrenceType: MonthYearRecurrenceType.dayOfMonth, + hasProps: (options) => { + if (!options.bymonthday) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday > 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.monthDay], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber( + dateStats.monthDay + )} day of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bymonthday) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday < 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.daysUntilEndMonth * -1], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndMonth + )} day of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInMonth], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ + dateStats.dayName + } of the month`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromMonthEnd * -1], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.weekdaysFromMonthEnd + )} ${dateStats.dayName} of the month`, + }, +]; + +export const YEAR_RECURRENCE_INFO: RecurrenceInfo[] = [ + { + recurrenceType: MonthYearRecurrenceType.dayOfMonth, + hasProps: (options) => { + if (!options.bymonthday || !options.bymonth) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday > 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.monthDay], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on ${dateStats.monthName} ${formatOrdinalNumber( + dateStats.monthDay + )}`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bymonthday || !options.bymonth) { + return false; + } + + const bymonthday = Array.isArray(options.bymonthday) + ? options.bymonthday[0] + : options.bymonthday; + + return bymonthday < 0; + }, + getProps: (dateStats) => ({ + bymonthday: [dateStats.daysUntilEndMonth * -1], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bymonthday: options.bymonthday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndMonth + )} day of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday || !options.bymonth) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInMonth], + byweekday: [dateStats.weekday], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInMonth)} ${ + dateStats.dayName + } of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfMonth, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday || !options.bymonth) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromMonthEnd * -1], + byweekday: [dateStats.weekday], + bymonth: [dateStats.month], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + bymonth: options.bymonth, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.weekdaysFromMonthEnd + )} ${dateStats.dayName} of ${dateStats.monthName}`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayOfYear, + hasProps: (options) => { + if (!options.byyearday) { + return false; + } + + const byyearday = Array.isArray(options.byyearday) + ? options.byyearday[0] + : options.byyearday; + + return byyearday > 0; + }, + getProps: (dateStats) => ({ + byyearday: [dateStats.yearDay], + }), + filterProps: (options) => ({ + byyearday: options.byyearday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.yearDay)} day of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.dayBeforeEndOfYear, + hasProps: (options) => { + if (!options.byyearday) { + return false; + } + + const byyearday = Array.isArray(options.byyearday) + ? options.byyearday[0] + : options.byyearday; + + return byyearday < 0; + }, + getProps: (dateStats) => ({ + byyearday: [dateStats.daysUntilEndYear * -1], + }), + filterProps: (options) => ({ + byyearday: options.byyearday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber( + dateStats.daysUntilEndYear + )} day of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayInYear, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos > 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdayInYear], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatOrdinalNumber(dateStats.weekdayInYear)} ${ + dateStats.dayName + } of the year`, + }, + { + recurrenceType: MonthYearRecurrenceType.weekdayBeforeEndOfYear, + hasProps: (options) => { + if (!options.bysetpos || !options.byweekday) { + return false; + } + + const bysetpos = Array.isArray(options.bysetpos) + ? options.bysetpos[0] + : options.bysetpos; + + return bysetpos < 0; + }, + getProps: (dateStats) => ({ + bysetpos: [dateStats.weekdaysFromYearEnd * -1], + byweekday: [dateStats.weekday], + }), + filterProps: (options) => ({ + bysetpos: options.bysetpos, + byweekday: options.byweekday, + }), + getDisplay: (dateStats) => + `on the ${formatLastOrdinalNumber(dateStats.weekdaysFromYearEnd)} ${ + dateStats.dayName + } of the year`, + }, +]; + +export const RECURRENCE_INFO_MAP: { [key: number]: RecurrenceInfo[] } = { + [RRule.MONTHLY]: MONTH_RECURRENCE_INFO, + [RRule.YEARLY]: YEAR_RECURRENCE_INFO, +}; From bd570580686cf88e078d0ad9980c207f69c1ecea Mon Sep 17 00:00:00 2001 From: Ben Skeen Date: Wed, 13 Sep 2023 13:23:25 -0600 Subject: [PATCH 4/9] I converted some console logs so they would only show up on verbose. --- src/ui/components/EditEventRecurrence.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ui/components/EditEventRecurrence.tsx b/src/ui/components/EditEventRecurrence.tsx index e58399ea..fba4904a 100644 --- a/src/ui/components/EditEventRecurrence.tsx +++ b/src/ui/components/EditEventRecurrence.tsx @@ -71,7 +71,7 @@ export const EditEventRecurrence = ({ byweekday: [defaultStartDate.weekday - 1], } as Options); - console.log("original:", recurrence?.origOptions); + console.debug("original:", recurrence?.origOptions); const currentStartDate = options.dtstart ? DateTime.fromJSDate(options.dtstart).toUTC() @@ -129,9 +129,9 @@ export const EditEventRecurrence = ({ const dtstart = options.dtstart; let otherOptions: Partial = {}; - console.log("Other:", otherOptions); - console.log("Updated:", updatedOptions); - console.log("Include Extra", includeExtra); + console.debug("Other:", otherOptions); + console.debug("Updated:", updatedOptions); + console.debug("Include Extra", includeExtra); if ((updatedEndType ?? endType) === "endDate") { otherOptions.until = currentEndDate.toJSDate(); @@ -159,7 +159,7 @@ export const EditEventRecurrence = ({ ...updatedOptions, }; - console.log("newProps", newProps); + console.debug("newProps", newProps); onChange?.(new RRule(newProps)); }, @@ -201,7 +201,7 @@ export const EditEventRecurrence = ({ ); } - console.log("extra updated:", extra); + console.debug("extra updated:", extra); } setCurrentExtraProps(extra); @@ -410,7 +410,7 @@ const DaySelect = ({ label={day.toString()} isSelected={value.includes(code)} onClick={() => { - console.log("Day Switched:", value, code); + console.debug("Day Switched:", value, code); value.includes(code) ? onChange(value.filter((c) => c !== code)) : onChange([code, ...value]); From 9d1d8226bab7308c1e765d3c8462e884edb258ab Mon Sep 17 00:00:00 2001 From: Ben Skeen Date: Wed, 13 Sep 2023 13:26:53 -0600 Subject: [PATCH 5/9] I removed one more console log. --- src/ui/components/EditEvent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/EditEvent.tsx b/src/ui/components/EditEvent.tsx index 575daddc..0af7008c 100644 --- a/src/ui/components/EditEvent.tsx +++ b/src/ui/components/EditEvent.tsx @@ -85,7 +85,7 @@ export const EditEvent = ({ const parsedDate = initialDate ? DateTime.fromFormat(initialDate, "yyyy-MM-dd") : DateTime.now(); - console.log("parsedDate:", parsedDate); + console.debug("parsedDate:", parsedDate); const [recurringRule, setRecurringRule] = useState( (initialEvent?.type === "rrule" && From e8abcb584cd1e090f7c62eca46e1134b520a7b85 Mon Sep 17 00:00:00 2001 From: Ben Skeen Date: Fri, 29 Sep 2023 08:01:12 -0600 Subject: [PATCH 6/9] I added a missing class to align the Year/Month select box with the other repeating controls. --- src/ui/components/EditEventRecurrence.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/components/EditEventRecurrence.tsx b/src/ui/components/EditEventRecurrence.tsx index fba4904a..0de273a4 100644 --- a/src/ui/components/EditEventRecurrence.tsx +++ b/src/ui/components/EditEventRecurrence.tsx @@ -445,6 +445,7 @@ const MonthYearSelect = ({

{ const selectedMonthYearType = Number(element.target.value); diff --git a/src/ui/components/event-recurrence-types.ts b/src/ui/components/event-recurrence-types.ts index 75482f95..833859df 100644 --- a/src/ui/components/event-recurrence-types.ts +++ b/src/ui/components/event-recurrence-types.ts @@ -1,6 +1,15 @@ import { DateTime } from "luxon"; import { Options, RRule } from "rrule"; +/** + * This file declares the different types of recurrence supported by + * this plugin, specifically for Monthly and Yearly recurrences. + */ + +/** + * This enum lists all of the possible types of recurrence, both for + * monthly and yearly recurrence. + */ export enum MonthYearRecurrenceType { dayOfMonth = 0, dayBeforeEndOfMonth = 1, @@ -12,6 +21,11 @@ export enum MonthYearRecurrenceType { weekdayBeforeEndOfYear = 7, } +/** + * An interface with the results from the getDateStats function. Contains + * any information that will be needed to calculate values for monthly or + * yearly recurrence. + */ export interface DateStats { monthDay: number; weekday: number; @@ -27,6 +41,14 @@ export interface DateStats { weekdaysFromYearEnd: number; } +/** + * Computes various values to be used in the recurrence rules for monthly + * or yearly recurrences. + * + * @param {DateTime} date - The date for which to compute statistics. + * @returns {DateStats} - Values related to the date, useful for computing + * recurrences. + */ export const getDateStats = (date: DateTime): DateStats => { const monthDay = Number(date.day); const daysInMonth = date.daysInMonth; @@ -49,6 +71,12 @@ export const getDateStats = (date: DateTime): DateStats => { }; }; +/** + * Formats the value as an ordinal. For example, 1 becomes '1st', 2 becomes + * '2nd', etc. + * @param {number} value - The value to be turned into an ordianl + * @returns {string} - The number as an ordinal. + */ export const formatOrdinalNumber = (value: number): string => { const tensValue = value % 100; const isBetween10And20 = tensValue > 10 && tensValue < 20; @@ -68,6 +96,17 @@ export const formatOrdinalNumber = (value: number): string => { return `${value}th`; }; +/** + * Formats the argument value as an ordinal, including the string 'to last' + * at the end. If the argument is 1, then the string 'last' will be + * returned. + * + * Used to format a number as an ordinal relative to the end of a time + * period (for example, the 2nd to last Friday of the month). + * @param {number} value - The value to be converted into an ordinal + * @returns {string} - The display string of an ordinal relative to the end + * of a time period. + */ export const formatLastOrdinalNumber = (value: number): string => { if (value === 1) { return "last"; @@ -76,14 +115,79 @@ export const formatLastOrdinalNumber = (value: number): string => { return `${formatOrdinalNumber(value)} to last`; }; +/** + * A RecurrenceInfo declares all of the necessary functionality to + * calculate/display different recurrence types in the Month/Year select + * box. + */ export interface RecurrenceInfo { + /** + * The type of recurrence declared by this RecurrenceInfo. The rest of + * the properties in this interface provide the necessary functionality + * to support this type of recurrence. + */ recurrenceType: MonthYearRecurrenceType; + + /** + * Returns a boolean declaring whether the passed-in RRule options + * contain all of the properties necessary for this recurrence type. + * + * Used to correctly display a selected recurrence type in the view on + * initialization. + * + * @param {Partial} options - The current options selected for + * the current recurrence. + * @returns {boolean} - True if the current options contain all the + * information needed for this recurrence type, false otherwise. + */ hasProps: (options: Partial) => boolean; + + /** + * Given the date stats for a certain date, this function will provide + * the RRule options necessary for this recurrence type. + * + * Used to provide the correct options for this recurrence type when + * the user selects it in the Month/Year select box. + * + * @param {DateStats} dateStats - The result of the getDateStats + * function for the necessary date. + * @returns {Partial} - The RRule options needed for this + * recurrence type. + */ getProps: (dateStats: DateStats) => Partial; + + /** + * Given a set of RRule options, this will return RRule options only + * including the ones needed for this recurrence type. + * + * Used when initializing the EditEventRecurrence component to the + * correct values for the current selected recurrence type. + * + * @param {Partial} options - The RRule options provided in the + * props to the EditEventRecurrence component. + * @returns {Partial} - The provided RRule options, only + * including the properties necessary for this recurrence type. + */ filterProps: (options: Partial) => Partial; + + /** + * Formats the display for this recurrence type. + * + * Used for the value displayed in the Month/Yearly recurrence select + * box. + * + * @param {DateStats} dateStats - The result of the getDateStats + * function for the necessary date. + * @returns {string} - The display string for this recurrence type. + */ getDisplay: (dateStats: DateStats) => string; } +/** + * The supported recurrences for monthly recurrence. This array can be + * updated if we need to support any additional monthly recurrence types in + * the future. + */ export const MONTH_RECURRENCE_INFO: RecurrenceInfo[] = [ { recurrenceType: MonthYearRecurrenceType.dayOfMonth, @@ -187,6 +291,11 @@ export const MONTH_RECURRENCE_INFO: RecurrenceInfo[] = [ }, ]; +/** + * The supported recurrences for yearly recurrence. This array can be + * updated if we need to support any additional monthly recurrence types in + * the future. + */ export const YEAR_RECURRENCE_INFO: RecurrenceInfo[] = [ { recurrenceType: MonthYearRecurrenceType.dayOfMonth, From 0963ca4ac079765b80d178618f2dbb2194e6fe9f Mon Sep 17 00:00:00 2001 From: Ben Skeen Date: Tue, 4 Jun 2024 09:09:05 -0600 Subject: [PATCH 8/9] I refactored the recurrence tests to match the requested format. --- .../format-ordinal-number.test.ts | 221 ++ .../month-day-before-end-of-month.test.ts | 115 + .../month-day-of-month.test.ts | 115 + .../month-weekday-before-end-of-month.test.ts | 156 ++ .../month-weekday-in-month.test.ts | 156 ++ .../event-recurrence-tests/test-helpers.ts | 23 + .../year-day-before-end-of-month.test.ts | 150 + .../year-day-before-end-of-year.test.ts | 115 + .../year-day-of-month.test.ts | 149 + .../year-day-of-year.test.ts | 115 + .../year-weekday-before-end-of-month.test.ts | 196 ++ .../year-weekday-before-end-of-year.test.ts | 156 ++ .../year-weekday-in-month.test.ts | 196 ++ .../year-weekday-in-year.test.ts | 156 ++ .../components/event-recurrence-types.test.ts | 2471 ----------------- 15 files changed, 2019 insertions(+), 2471 deletions(-) create mode 100644 src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts create mode 100644 src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/month-day-of-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/test-helpers.ts create mode 100644 src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts create mode 100644 src/ui/components/event-recurrence-tests/year-day-of-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/year-day-of-year.test.ts create mode 100644 src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts create mode 100644 src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts create mode 100644 src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts delete mode 100644 src/ui/components/event-recurrence-types.test.ts diff --git a/src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts b/src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts new file mode 100644 index 00000000..ac04a1c5 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts @@ -0,0 +1,221 @@ +import { formatOrdinalNumber } from "../event-recurrence-types"; + +describe("formatOrdinalNumber", () => { + describe("when value ends in 1", () => { + describe("when the tens place is zero", () => { + describe("when the value is 1", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(1); + + expect(result).toBe("1st"); + }); + }); + + describe("when the value is 101", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(101); + + expect(result).toBe("101st"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 11", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(11); + + expect(result).toBe("11th"); + }); + }); + + describe("when the value is 111", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(111); + + expect(result).toBe("111th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 21", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(21); + + expect(result).toBe("21st"); + }); + }); + + describe("when the value is 121", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(121); + + expect(result).toBe("121st"); + }); + }); + }); + }); + + describe("when value ends in 2", () => { + describe("when the tens place is zero", () => { + describe("when the value is 2", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(2); + + expect(result).toBe("2nd"); + }); + }); + + describe("when the value is 102", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(102); + + expect(result).toBe("102nd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 12", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(12); + + expect(result).toBe("12th"); + }); + }); + + describe("when the value is 112", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(112); + + expect(result).toBe("112th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 22", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(22); + + expect(result).toBe("22nd"); + }); + }); + + describe("when the value is 122", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(122); + + expect(result).toBe("122nd"); + }); + }); + }); + }); + + describe("when value ends in 3", () => { + describe("when the tens place is zero", () => { + describe("when the value is 3", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(3); + + expect(result).toBe("3rd"); + }); + }); + + describe("when the value is 103", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(103); + + expect(result).toBe("103rd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 13", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(13); + + expect(result).toBe("13th"); + }); + }); + + describe("when the value is 113", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(113); + + expect(result).toBe("113th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 23", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(23); + + expect(result).toBe("23rd"); + }); + }); + + describe("when the value is 123", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(123); + + expect(result).toBe("123rd"); + }); + }); + }); + }); + + describe("when the value does not end in 1, 2 or 3", () => { + describe("when the value is 7", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(7); + + expect(result).toBe("7th"); + }); + }); + + describe("when the value is 17", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(17); + + expect(result).toBe("17th"); + }); + }); + + describe("when the value is 27", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(27); + + expect(result).toBe("27th"); + }); + }); + + describe("when the value is 107", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(107); + + expect(result).toBe("107th"); + }); + }); + + describe("when the value is 117", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(117); + + expect(result).toBe("117th"); + }); + }); + + describe("when the value is 127", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(127); + + expect(result).toBe("127th"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts new file mode 100644 index 00000000..9066e8de --- /dev/null +++ b/src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts @@ -0,0 +1,115 @@ +import { DateTime } from "luxon"; +import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> dayBeforeEndOfMonth", () => { + const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayBeforeEndOfMonth) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats daysBeforeEnd in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([-30]); + }); + }); + + describe("filterProps", () => { + it("should only return the bymonthday option in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject([ + "bymonthday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 19th to last day of the month" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-day-of-month.test.ts b/src/ui/components/event-recurrence-tests/month-day-of-month.test.ts new file mode 100644 index 00000000..4674b0fe --- /dev/null +++ b/src/ui/components/event-recurrence-tests/month-day-of-month.test.ts @@ -0,0 +1,115 @@ +import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; +import { DateTime } from "luxon"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> dayOfMonth", () => { + const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayOfMonth) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats month day in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([1]); + }); + }); + + describe("filterProps", () => { + it("should only return the bymonthday option in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject([ + "bymonthday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 12th day of the month" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts new file mode 100644 index 00000000..dae8291f --- /dev/null +++ b/src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts @@ -0,0 +1,156 @@ +import { DateTime } from "luxon"; +import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> weekdayBeforeEndOfMonth", () => { + const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayBeforeEndOfMonth) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdaysFromMonthEnd and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdaysFromMonthEnd * -1, + ]); + + expect(result.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 3rd to last Tuesday of the month" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts b/src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts new file mode 100644 index 00000000..0f58e681 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts @@ -0,0 +1,156 @@ +import { DateTime } from "luxon"; +import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> weekdayInMonth", () => { + const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayInMonth) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdayInMonth and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdayInMonth, + ]); + + expect(result.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 2nd Tuesday of the month" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/test-helpers.ts b/src/ui/components/event-recurrence-tests/test-helpers.ts new file mode 100644 index 00000000..76469d59 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/test-helpers.ts @@ -0,0 +1,23 @@ +import { Options, RRule } from "rrule"; + +export const defaultOptions: Partial = { + freq: RRule.DAILY, + dtstart: new Date(), + interval: 1, + wkst: RRule.SU, + count: 1, + until: new Date(), + tzid: "UTC", + bysetpos: [1], + bymonth: [1], + bymonthday: [2], + bynmonthday: [3], + byyearday: [20], + byweekno: [1], + byweekday: [RRule.WE], + bynweekday: [[1]], + byhour: [2], + byminute: [42], + bysecond: [21], + byeaster: 1, +}; diff --git a/src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts new file mode 100644 index 00000000..80fdbd87 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts @@ -0,0 +1,150 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayBeforeEndOfMonth", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayBeforeEndOfMonth) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: [-1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: 1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: -1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = { + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats days until end of month and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([-30]); + expect(result.bymonth).toMatchObject([9]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the two expected properties", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bymonthday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 19th to last day of September" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts b/src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts new file mode 100644 index 00000000..6a937aaf --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts @@ -0,0 +1,115 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayBeforeEndOfYear", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayBeforeEndOfYear) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2024-01-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return false", () => { + const options = { + byyearday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return true", () => { + const options = { + byyearday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + it("should return false", () => { + const options = { + byyearday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + it("should return true", () => { + const options = { + byyearday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + byyearday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats days until end of year in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.byyearday).toMatchObject([-366]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the expected property", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject([ + "byyearday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2024-01-01") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 366th to last day of the year" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-day-of-month.test.ts b/src/ui/components/event-recurrence-tests/year-day-of-month.test.ts new file mode 100644 index 00000000..c4efee26 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-day-of-month.test.ts @@ -0,0 +1,149 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayOfMonth", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayOfMonth) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: [-1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: 1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: -1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = { + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats month day and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([1]); + + expect(result.bymonth).toMatchObject([9]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the two expected properties", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bymonthday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on September 12th"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-day-of-year.test.ts b/src/ui/components/event-recurrence-tests/year-day-of-year.test.ts new file mode 100644 index 00000000..497877d7 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-day-of-year.test.ts @@ -0,0 +1,115 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayOfYear", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayOfYear) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-01-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return true", () => { + const options = { + byyearday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return false", () => { + const options = { + byyearday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + it("should return true", () => { + const options = { + byyearday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + it("should return false", () => { + const options = { + byyearday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + byyearday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats year day in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.byyearday).toMatchObject([1]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the expected property", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject([ + "byyearday", + ]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2024-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 256th day of the year" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts new file mode 100644 index 00000000..c9e489cf --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts @@ -0,0 +1,196 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayBeforeEndOfMonth", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayBeforeEndOfMonth) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdaysFromMonthEnd, weekday and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdaysFromMonthEnd * -1, + ]); + + expect(result.byweekday).toMatchObject([ + dateStats.weekday, + ]); + + expect(result.bymonth).toMatchObject([ + dateStats.month, + ]); + }); + }); + + describe("filterProps", () => { + it("should only return the three expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(3); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 3rd to last Tuesday of September" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts new file mode 100644 index 00000000..854fbdd5 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts @@ -0,0 +1,156 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayBeforeEndOfYear", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayBeforeEndOfYear) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdaysFromYearEnd and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdaysFromYearEnd * -1, + ]); + + expect(result.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2024-01-01") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 53rd to last Monday of the year" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts new file mode 100644 index 00000000..0c234588 --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts @@ -0,0 +1,196 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayInMonth", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayInMonth) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdayInMonth, weekday and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdayInMonth, + ]); + + expect(result.byweekday).toMatchObject([ + dateStats.weekday, + ]); + + expect(result.bymonth).toMatchObject([ + dateStats.month, + ]); + }); + }); + + describe("filterProps", () => { + it("should only return the three expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(3); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 2nd Tuesday of September" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts new file mode 100644 index 00000000..90b2fc4a --- /dev/null +++ b/src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts @@ -0,0 +1,156 @@ +import { DateTime } from "luxon"; +import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayInYear", () => { + const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayInYear) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdayInYear and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdayInYear, + ]); + + expect(result.byweekday).toMatchObject([ + dateStats.weekday, + ]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe( + "on the 37th Tuesday of the year" + ); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-types.test.ts b/src/ui/components/event-recurrence-types.test.ts deleted file mode 100644 index 6c259a46..00000000 --- a/src/ui/components/event-recurrence-types.test.ts +++ /dev/null @@ -1,2471 +0,0 @@ -import { Options, RRule } from "rrule"; -import { DateTime } from "luxon"; -import { - DateStats, - MONTH_RECURRENCE_INFO, - MonthYearRecurrenceType, - RecurrenceInfo, - YEAR_RECURRENCE_INFO, - formatOrdinalNumber, - getDateStats, -} from "./event-recurrence-types"; - -describe("Recurrence Info", () => { - let currentInfo: RecurrenceInfo; - let options: Partial = { - freq: RRule.DAILY, - dtstart: new Date(), - interval: 1, - wkst: RRule.SU, - count: 1, - until: new Date(), - tzid: "UTC", - bysetpos: [1], - bymonth: [1], - bymonthday: [2], - bynmonthday: [3], - byyearday: [20], - byweekno: [1], - byweekday: [RRule.WE], - bynweekday: [[1]], - byhour: [2], - byminute: [42], - bysecond: [21], - byeaster: 1, - }; - - let dateStats: DateStats; - - let hasPropsResult: boolean; - let optionsResult: Partial; - let displayResult: string; - - let getRecurrenceInfo: (type: MonthYearRecurrenceType) => void; - - const actHasProps = () => { - hasPropsResult = currentInfo.hasProps(options); - }; - - const actGetProps = () => { - optionsResult = currentInfo.getProps(dateStats); - }; - - const actFilterProps = () => { - optionsResult = currentInfo.filterProps(options); - }; - - const actGetDisplay = () => { - displayResult = currentInfo.getDisplay(dateStats); - }; - - describe("MONTH_RECURRENCE_INFO", () => { - beforeEach(() => { - getRecurrenceInfo = (type) => { - currentInfo = - MONTH_RECURRENCE_INFO.find( - (info) => info.recurrenceType === type - ) ?? MONTH_RECURRENCE_INFO[0]; - }; - }); - - describe("dayOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayOfMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - bymonthday: [1], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - bymonthday: 1, - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = {}; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - bymonthday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats month day in the options", () => { - expect(optionsResult.bymonthday).toMatchObject([1]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return the bymonthday option in the options", () => { - expect(Object.keys(optionsResult)).toMatchObject([ - "bymonthday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 12th day of the month" - ); - }); - }); - }); - }); - - describe("dayBeforeEndOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - bymonthday: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - bymonthday: 1, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = {}; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - bymonthday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats daysBeforeEnd in the options", () => { - expect(optionsResult.bymonthday).toMatchObject([-30]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return the bymonthday option in the options", () => { - expect(Object.keys(optionsResult)).toMatchObject([ - "bymonthday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 19th to last day of the month" - ); - }); - }); - }); - }); - - describe("weekdayInMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.weekdayInMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdayInMonth in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdayInMonth, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return two keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 2nd Tuesday of the month" - ); - }); - }); - }); - }); - - describe("weekdayBeforeEndOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo( - MonthYearRecurrenceType.weekdayBeforeEndOfMonth - ); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdaysFromMonthEnd in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdaysFromMonthEnd * -1, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return two keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 3rd to last Tuesday of the month" - ); - }); - }); - }); - }); - }); - - describe("YEAR_RECURRENCE_INFO", () => { - beforeEach(() => { - getRecurrenceInfo = (type) => { - currentInfo = - YEAR_RECURRENCE_INFO.find( - (info) => info.recurrenceType === type - ) ?? YEAR_RECURRENCE_INFO[0]; - }; - }); - - describe("dayOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayOfMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - bymonthday: [1], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - bymonthday: 1, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = { - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - bymonthday: null, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is undefined", () => { - beforeEach(() => { - options = { - bymonthday: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is null", () => { - beforeEach(() => { - options = { - bymonthday: [1], - bymonth: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats month day in the options", () => { - expect(optionsResult.bymonthday).toMatchObject([1]); - }); - - it("should put the dateStats month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([9]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should return an options with two properties", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return an options with bymonthday", () => { - expect(Object.keys(optionsResult)).toContain("bymonthday"); - }); - - it("should return an options with bymonth", () => { - expect(Object.keys(optionsResult)).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe("on September 12th"); - }); - }); - }); - }); - - describe("dayBeforeEndOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - bymonthday: [1], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - bymonthday: 1, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = { - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - bymonthday: null, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is undefined", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is null", () => { - beforeEach(() => { - options = { - bymonthday: [-1], - bymonth: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats days until end of month in the options", () => { - expect(optionsResult.bymonthday).toMatchObject([-30]); - }); - - it("should put the dateStats month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([9]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should return an options with two properties", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return an options with bymonthday", () => { - expect(Object.keys(optionsResult)).toContain("bymonthday"); - }); - - it("should return an options with bymonth", () => { - expect(Object.keys(optionsResult)).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 19th to last day of September" - ); - }); - }); - }); - }); - - describe("weekdayInMonth", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.weekdayInMonth); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdayInMonth in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdayInMonth, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - - it("should put the month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([ - dateStats.month, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return three keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(3); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - - it("should return options that contain bymonth", () => { - expect(Object.keys(optionsResult)).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 2nd Tuesday of September" - ); - }); - }); - }); - }); - - describe("weekdayBeforeEndOfMonth", () => { - beforeEach(() => { - getRecurrenceInfo( - MonthYearRecurrenceType.weekdayBeforeEndOfMonth - ); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - bymonth: [9], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when month is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdaysFromMonthEnd in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdaysFromMonthEnd * -1, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - - it("should put the month in the options", () => { - expect(optionsResult.bymonth).toMatchObject([ - dateStats.month, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return three keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(3); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - - it("should return options that contain bymonth", () => { - expect(Object.keys(optionsResult)).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 3rd to last Tuesday of September" - ); - }); - }); - }); - }); - - describe("dayOfYear", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayOfYear); - dateStats = getDateStats(DateTime.fromISO("2023-01-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - byyearday: [1], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - byyearday: [-1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - byyearday: 1, - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - byyearday: -1, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = {}; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - byyearday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats year day in the options", () => { - expect(optionsResult.byyearday).toMatchObject([1]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should return an options with the expected property", () => { - expect(Object.keys(optionsResult)).toMatchObject([ - "byyearday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2024-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 256th day of the year" - ); - }); - }); - }); - }); - - describe("dayBeforeEndOfYear", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.dayBeforeEndOfYear); - dateStats = getDateStats(DateTime.fromISO("2024-01-01")); - }); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - beforeEach(() => { - options = { - byyearday: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is an array with a negative number", () => { - beforeEach(() => { - options = { - byyearday: [-1], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is a positive number", () => { - beforeEach(() => { - options = { - byyearday: 1, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is a negative number", () => { - beforeEach(() => { - options = { - byyearday: -1, - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when day is undefined", () => { - beforeEach(() => { - options = {}; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when day is null", () => { - beforeEach(() => { - options = { - byyearday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the dateStats days until end of year in the options", () => { - expect(optionsResult.byyearday).toMatchObject([-366]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should return an options with the expected property", () => { - expect(Object.keys(optionsResult)).toMatchObject([ - "byyearday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2024-01-01") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 366th to last day of the year" - ); - }); - }); - }); - }); - - describe("weekdayInYear", () => { - beforeEach(() => { - getRecurrenceInfo(MonthYearRecurrenceType.weekdayInYear); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bymonthday: -1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdayInYear in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdayInYear, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return two keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 37th Tuesday of the year" - ); - }); - }); - }); - }); - - describe("weekdayBeforeEndOfYear", () => { - beforeEach(() => { - getRecurrenceInfo( - MonthYearRecurrenceType.weekdayBeforeEndOfYear - ); - dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - }); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - beforeEach(() => { - options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is a positive number", () => { - beforeEach(() => { - options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is a negative number", () => { - beforeEach(() => { - options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return true", () => { - expect(hasPropsResult).toBe(true); - }); - }); - - describe("when bysetpos is undefined", () => { - beforeEach(() => { - options = { - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - beforeEach(() => { - options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - beforeEach(() => { - options = { - bysetpos: [1], - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - - describe("when weekday is null", () => { - beforeEach(() => { - options = { - bysetpos: [1], - byweekday: null, - }; - - actHasProps(); - }); - - it("should return false", () => { - expect(hasPropsResult).toBe(false); - }); - }); - }); - - describe("getProps", () => { - beforeEach(() => { - actGetProps(); - }); - - it("should put the weekdaysFromYearEnd in the options", () => { - expect(optionsResult.bysetpos).toMatchObject([ - dateStats.weekdaysFromYearEnd * -1, - ]); - }); - - it("should put the weekday in the options", () => { - expect(optionsResult.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - beforeEach(() => { - actFilterProps(); - }); - - it("should only return two keys in the options", () => { - expect(Object.keys(optionsResult)).toHaveLength(2); - }); - - it("should return options that contain bysetpos", () => { - expect(Object.keys(optionsResult)).toContain("bysetpos"); - }); - - it("should return options that contain byweekday", () => { - expect(Object.keys(optionsResult)).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - beforeEach(() => { - dateStats = getDateStats( - DateTime.fromISO("2024-01-01") - ); - - actGetDisplay(); - }); - - it("should return the expected text", () => { - expect(displayResult).toBe( - "on the 53rd to last Monday of the year" - ); - }); - }); - }); - }); - }); -}); - -describe("formatOrdinalNumber", () => { - let value: number; - let result: string; - - const act = () => { - result = formatOrdinalNumber(value); - }; - - describe("when value ends in 1", () => { - describe("when the tens place is zero", () => { - describe("when the value is 1", () => { - beforeEach(() => { - value = 1; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("1st"); - }); - }); - - describe("when the value is 101", () => { - beforeEach(() => { - value = 101; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("101st"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 11", () => { - beforeEach(() => { - value = 11; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("11th"); - }); - }); - - describe("when the value is 111", () => { - beforeEach(() => { - value = 111; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("111th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 21", () => { - beforeEach(() => { - value = 21; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("21st"); - }); - }); - - describe("when the value is 121", () => { - beforeEach(() => { - value = 121; - - act(); - }); - - it("should end in st", () => { - expect(result).toBe("121st"); - }); - }); - }); - }); - - describe("when value ends in 2", () => { - describe("when the tens place is zero", () => { - describe("when the value is 2", () => { - beforeEach(() => { - value = 2; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("2nd"); - }); - }); - - describe("when the value is 102", () => { - beforeEach(() => { - value = 102; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("102nd"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 12", () => { - beforeEach(() => { - value = 12; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("12th"); - }); - }); - - describe("when the value is 112", () => { - beforeEach(() => { - value = 112; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("112th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 22", () => { - beforeEach(() => { - value = 22; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("22nd"); - }); - }); - - describe("when the value is 122", () => { - beforeEach(() => { - value = 122; - - act(); - }); - - it("should end in nd", () => { - expect(result).toBe("122nd"); - }); - }); - }); - }); - - describe("when value ends in 3", () => { - describe("when the tens place is zero", () => { - describe("when the value is 3", () => { - beforeEach(() => { - value = 3; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("3rd"); - }); - }); - - describe("when the value is 103", () => { - beforeEach(() => { - value = 103; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("103rd"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 13", () => { - beforeEach(() => { - value = 13; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("13th"); - }); - }); - - describe("when the value is 113", () => { - beforeEach(() => { - value = 113; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("113th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 23", () => { - beforeEach(() => { - value = 23; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("23rd"); - }); - }); - - describe("when the value is 123", () => { - beforeEach(() => { - value = 123; - - act(); - }); - - it("should end in rd", () => { - expect(result).toBe("123rd"); - }); - }); - }); - }); - - describe("when the value does not end in 1, 2 or 3", () => { - describe("when the value is 7", () => { - beforeEach(() => { - value = 7; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("7th"); - }); - }); - - describe("when the value is 17", () => { - beforeEach(() => { - value = 17; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("17th"); - }); - }); - - describe("when the value is 27", () => { - beforeEach(() => { - value = 27; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("27th"); - }); - }); - - describe("when the value is 107", () => { - beforeEach(() => { - value = 107; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("107th"); - }); - }); - - describe("when the value is 117", () => { - beforeEach(() => { - value = 117; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("117th"); - }); - }); - - describe("when the value is 127", () => { - beforeEach(() => { - value = 127; - - act(); - }); - - it("should end in th", () => { - expect(result).toBe("127th"); - }); - }); - }); -}); From 454e7cb2de0b286c691c8004f5fb473f0271b1aa Mon Sep 17 00:00:00 2001 From: Ben Skeen Date: Tue, 4 Jun 2024 09:12:42 -0600 Subject: [PATCH 9/9] I added some formatting changes that were mised in the last commit. --- .../format-ordinal-number.test.ts | 442 +++++++++--------- .../month-day-before-end-of-month.test.ts | 235 +++++----- .../month-day-of-month.test.ts | 233 ++++----- .../month-weekday-before-end-of-month.test.ts | 317 ++++++------- .../month-weekday-in-month.test.ts | 314 ++++++------- .../event-recurrence-tests/test-helpers.ts | 46 +- .../year-day-before-end-of-month.test.ts | 307 ++++++------ .../year-day-before-end-of-year.test.ts | 235 +++++----- .../year-day-of-month.test.ts | 305 ++++++------ .../year-day-of-year.test.ts | 233 ++++----- .../year-weekday-before-end-of-month.test.ts | 395 ++++++++-------- .../year-weekday-before-end-of-year.test.ts | 317 ++++++------- .../year-weekday-in-month.test.ts | 392 ++++++++-------- .../year-weekday-in-year.test.ts | 314 ++++++------- 14 files changed, 2066 insertions(+), 2019 deletions(-) diff --git a/src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts b/src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts index ac04a1c5..b92c0a2a 100644 --- a/src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts +++ b/src/ui/components/event-recurrence-tests/format-ordinal-number.test.ts @@ -1,221 +1,221 @@ -import { formatOrdinalNumber } from "../event-recurrence-types"; - -describe("formatOrdinalNumber", () => { - describe("when value ends in 1", () => { - describe("when the tens place is zero", () => { - describe("when the value is 1", () => { - it("should end in st", () => { - const result = formatOrdinalNumber(1); - - expect(result).toBe("1st"); - }); - }); - - describe("when the value is 101", () => { - it("should end in st", () => { - const result = formatOrdinalNumber(101); - - expect(result).toBe("101st"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 11", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(11); - - expect(result).toBe("11th"); - }); - }); - - describe("when the value is 111", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(111); - - expect(result).toBe("111th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 21", () => { - it("should end in st", () => { - const result = formatOrdinalNumber(21); - - expect(result).toBe("21st"); - }); - }); - - describe("when the value is 121", () => { - it("should end in st", () => { - const result = formatOrdinalNumber(121); - - expect(result).toBe("121st"); - }); - }); - }); - }); - - describe("when value ends in 2", () => { - describe("when the tens place is zero", () => { - describe("when the value is 2", () => { - it("should end in nd", () => { - const result = formatOrdinalNumber(2); - - expect(result).toBe("2nd"); - }); - }); - - describe("when the value is 102", () => { - it("should end in nd", () => { - const result = formatOrdinalNumber(102); - - expect(result).toBe("102nd"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 12", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(12); - - expect(result).toBe("12th"); - }); - }); - - describe("when the value is 112", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(112); - - expect(result).toBe("112th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 22", () => { - it("should end in nd", () => { - const result = formatOrdinalNumber(22); - - expect(result).toBe("22nd"); - }); - }); - - describe("when the value is 122", () => { - it("should end in nd", () => { - const result = formatOrdinalNumber(122); - - expect(result).toBe("122nd"); - }); - }); - }); - }); - - describe("when value ends in 3", () => { - describe("when the tens place is zero", () => { - describe("when the value is 3", () => { - it("should end in rd", () => { - const result = formatOrdinalNumber(3); - - expect(result).toBe("3rd"); - }); - }); - - describe("when the value is 103", () => { - it("should end in rd", () => { - const result = formatOrdinalNumber(103); - - expect(result).toBe("103rd"); - }); - }); - }); - - describe("when the tens place is one", () => { - describe("when the value is 13", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(13); - - expect(result).toBe("13th"); - }); - }); - - describe("when the value is 113", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(113); - - expect(result).toBe("113th"); - }); - }); - }); - - describe("when the tens place is two", () => { - describe("when the value is 23", () => { - it("should end in rd", () => { - const result = formatOrdinalNumber(23); - - expect(result).toBe("23rd"); - }); - }); - - describe("when the value is 123", () => { - it("should end in rd", () => { - const result = formatOrdinalNumber(123); - - expect(result).toBe("123rd"); - }); - }); - }); - }); - - describe("when the value does not end in 1, 2 or 3", () => { - describe("when the value is 7", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(7); - - expect(result).toBe("7th"); - }); - }); - - describe("when the value is 17", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(17); - - expect(result).toBe("17th"); - }); - }); - - describe("when the value is 27", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(27); - - expect(result).toBe("27th"); - }); - }); - - describe("when the value is 107", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(107); - - expect(result).toBe("107th"); - }); - }); - - describe("when the value is 117", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(117); - - expect(result).toBe("117th"); - }); - }); - - describe("when the value is 127", () => { - it("should end in th", () => { - const result = formatOrdinalNumber(127); - - expect(result).toBe("127th"); - }); - }); - }); -}); +import { formatOrdinalNumber } from "../event-recurrence-types"; + +describe("formatOrdinalNumber", () => { + describe("when value ends in 1", () => { + describe("when the tens place is zero", () => { + describe("when the value is 1", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(1); + + expect(result).toBe("1st"); + }); + }); + + describe("when the value is 101", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(101); + + expect(result).toBe("101st"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 11", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(11); + + expect(result).toBe("11th"); + }); + }); + + describe("when the value is 111", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(111); + + expect(result).toBe("111th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 21", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(21); + + expect(result).toBe("21st"); + }); + }); + + describe("when the value is 121", () => { + it("should end in st", () => { + const result = formatOrdinalNumber(121); + + expect(result).toBe("121st"); + }); + }); + }); + }); + + describe("when value ends in 2", () => { + describe("when the tens place is zero", () => { + describe("when the value is 2", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(2); + + expect(result).toBe("2nd"); + }); + }); + + describe("when the value is 102", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(102); + + expect(result).toBe("102nd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 12", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(12); + + expect(result).toBe("12th"); + }); + }); + + describe("when the value is 112", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(112); + + expect(result).toBe("112th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 22", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(22); + + expect(result).toBe("22nd"); + }); + }); + + describe("when the value is 122", () => { + it("should end in nd", () => { + const result = formatOrdinalNumber(122); + + expect(result).toBe("122nd"); + }); + }); + }); + }); + + describe("when value ends in 3", () => { + describe("when the tens place is zero", () => { + describe("when the value is 3", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(3); + + expect(result).toBe("3rd"); + }); + }); + + describe("when the value is 103", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(103); + + expect(result).toBe("103rd"); + }); + }); + }); + + describe("when the tens place is one", () => { + describe("when the value is 13", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(13); + + expect(result).toBe("13th"); + }); + }); + + describe("when the value is 113", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(113); + + expect(result).toBe("113th"); + }); + }); + }); + + describe("when the tens place is two", () => { + describe("when the value is 23", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(23); + + expect(result).toBe("23rd"); + }); + }); + + describe("when the value is 123", () => { + it("should end in rd", () => { + const result = formatOrdinalNumber(123); + + expect(result).toBe("123rd"); + }); + }); + }); + }); + + describe("when the value does not end in 1, 2 or 3", () => { + describe("when the value is 7", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(7); + + expect(result).toBe("7th"); + }); + }); + + describe("when the value is 17", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(17); + + expect(result).toBe("17th"); + }); + }); + + describe("when the value is 27", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(27); + + expect(result).toBe("27th"); + }); + }); + + describe("when the value is 107", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(107); + + expect(result).toBe("107th"); + }); + }); + + describe("when the value is 117", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(117); + + expect(result).toBe("117th"); + }); + }); + + describe("when the value is 127", () => { + it("should end in th", () => { + const result = formatOrdinalNumber(127); + + expect(result).toBe("127th"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts index 9066e8de..48b47efc 100644 --- a/src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts +++ b/src/ui/components/event-recurrence-tests/month-day-before-end-of-month.test.ts @@ -1,115 +1,120 @@ -import { DateTime } from "luxon"; -import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; -import { defaultOptions } from "./test-helpers"; - -describe("Monthly Recurrence -> dayBeforeEndOfMonth", () => { - const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayBeforeEndOfMonth) ?? MONTH_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - it("should return false", () => { - const options = { - bymonthday: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is an array with a negative number", () => { - it("should return true", () => { - const options = { - bymonthday: [-1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is a positive number", () => { - it("should return false", () => { - const options = { - bymonthday: 1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is a negative number", () => { - it("should return true", () => { - const options = { - bymonthday: -1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is undefined", () => { - it("should return false", () => { - const options = {}; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is null", () => { - it("should return false", () => { - const options = { - bymonthday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the dateStats daysBeforeEnd in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bymonthday).toMatchObject([-30]); - }); - }); - - describe("filterProps", () => { - it("should only return the bymonthday option in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - expect(Object.keys(result)).toMatchObject([ - "bymonthday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 19th to last day of the month" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MONTH_RECURRENCE_INFO, + MonthYearRecurrenceType, + getDateStats, +} from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> dayBeforeEndOfMonth", () => { + const currentInfo = + MONTH_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === + MonthYearRecurrenceType.dayBeforeEndOfMonth + ) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats daysBeforeEnd in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([-30]); + }); + }); + + describe("filterProps", () => { + it("should only return the bymonthday option in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject(["bymonthday"]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 19th to last day of the month"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-day-of-month.test.ts b/src/ui/components/event-recurrence-tests/month-day-of-month.test.ts index 4674b0fe..dd5a97ef 100644 --- a/src/ui/components/event-recurrence-tests/month-day-of-month.test.ts +++ b/src/ui/components/event-recurrence-tests/month-day-of-month.test.ts @@ -1,115 +1,118 @@ -import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; -import { DateTime } from "luxon"; -import { defaultOptions } from "./test-helpers"; - -describe("Monthly Recurrence -> dayOfMonth", () => { - const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayOfMonth) ?? MONTH_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - it("should return true", () => { - const options = { - bymonthday: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is an array with a negative number", () => { - it("should return false", () => { - const options = { - bymonthday: [-1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is a positive number", () => { - it("should return true", () => { - const options = { - bymonthday: 1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is a negative number", () => { - it("should return false", () => { - const options = { - bymonthday: -1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is undefined", () => { - it("should return false", () => { - const options = {}; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is null", () => { - it("should return false", () => { - const options = { - bymonthday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the dateStats month day in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bymonthday).toMatchObject([1]); - }); - }); - - describe("filterProps", () => { - it("should only return the bymonthday option in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - expect(Object.keys(result)).toMatchObject([ - "bymonthday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 12th day of the month" - ); - }); - }); - }); -}); +import { + MONTH_RECURRENCE_INFO, + MonthYearRecurrenceType, + getDateStats, +} from "../event-recurrence-types"; +import { DateTime } from "luxon"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> dayOfMonth", () => { + const currentInfo = + MONTH_RECURRENCE_INFO.find( + (info) => info.recurrenceType === MonthYearRecurrenceType.dayOfMonth + ) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats month day in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([1]); + }); + }); + + describe("filterProps", () => { + it("should only return the bymonthday option in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject(["bymonthday"]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 12th day of the month"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts index dae8291f..0e0f21ee 100644 --- a/src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts +++ b/src/ui/components/event-recurrence-tests/month-weekday-before-end-of-month.test.ts @@ -1,156 +1,161 @@ -import { DateTime } from "luxon"; -import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; -import { RRule } from "rrule"; -import { defaultOptions } from "./test-helpers"; - -describe("Monthly Recurrence -> weekdayBeforeEndOfMonth", () => { - const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayBeforeEndOfMonth) ?? MONTH_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - it("should return true", () => { - const options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is a positive number", () => { - it("should return false", () => { - const options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is a negative number", () => { - it("should return true", () => { - const options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is undefined", () => { - it("should return false", () => { - const options = { - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - it("should return false", () => { - const options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the weekdaysFromMonthEnd and weekday in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bysetpos).toMatchObject([ - dateStats.weekdaysFromMonthEnd * -1, - ]); - - expect(result.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - it("should only return the two expected keys in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(2); - expect(resultKeys).toContain("bysetpos"); - expect(resultKeys).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 3rd to last Tuesday of the month" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MONTH_RECURRENCE_INFO, + MonthYearRecurrenceType, + getDateStats, +} from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> weekdayBeforeEndOfMonth", () => { + const currentInfo = + MONTH_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === + MonthYearRecurrenceType.weekdayBeforeEndOfMonth + ) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdaysFromMonthEnd and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdaysFromMonthEnd * -1, + ]); + + expect(result.byweekday).toMatchObject([dateStats.weekday]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 3rd to last Tuesday of the month"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts b/src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts index 0f58e681..7bdd6768 100644 --- a/src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts +++ b/src/ui/components/event-recurrence-tests/month-weekday-in-month.test.ts @@ -1,156 +1,158 @@ -import { DateTime } from "luxon"; -import { MONTH_RECURRENCE_INFO, MonthYearRecurrenceType, getDateStats } from "../event-recurrence-types"; -import { RRule } from "rrule"; -import { defaultOptions } from "./test-helpers"; - -describe("Monthly Recurrence -> weekdayInMonth", () => { - const currentInfo = MONTH_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayInMonth) ?? MONTH_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - it("should return true", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - it("should return false", () => { - const options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is a positive number", () => { - it("should return true", () => { - const options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is a negative number", () => { - it("should return false", () => { - const options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is undefined", () => { - it("should return false", () => { - const options = { - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - it("should return false", () => { - const options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the weekdayInMonth and weekday in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bysetpos).toMatchObject([ - dateStats.weekdayInMonth, - ]); - - expect(result.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - it("should only return the two expected keys in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(2); - expect(resultKeys).toContain("bysetpos"); - expect(resultKeys).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 2nd Tuesday of the month" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MONTH_RECURRENCE_INFO, + MonthYearRecurrenceType, + getDateStats, +} from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Monthly Recurrence -> weekdayInMonth", () => { + const currentInfo = + MONTH_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === MonthYearRecurrenceType.weekdayInMonth + ) ?? MONTH_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdayInMonth and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([dateStats.weekdayInMonth]); + + expect(result.byweekday).toMatchObject([dateStats.weekday]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 2nd Tuesday of the month"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/test-helpers.ts b/src/ui/components/event-recurrence-tests/test-helpers.ts index 76469d59..2e710e7a 100644 --- a/src/ui/components/event-recurrence-tests/test-helpers.ts +++ b/src/ui/components/event-recurrence-tests/test-helpers.ts @@ -1,23 +1,23 @@ -import { Options, RRule } from "rrule"; - -export const defaultOptions: Partial = { - freq: RRule.DAILY, - dtstart: new Date(), - interval: 1, - wkst: RRule.SU, - count: 1, - until: new Date(), - tzid: "UTC", - bysetpos: [1], - bymonth: [1], - bymonthday: [2], - bynmonthday: [3], - byyearday: [20], - byweekno: [1], - byweekday: [RRule.WE], - bynweekday: [[1]], - byhour: [2], - byminute: [42], - bysecond: [21], - byeaster: 1, -}; +import { Options, RRule } from "rrule"; + +export const defaultOptions: Partial = { + freq: RRule.DAILY, + dtstart: new Date(), + interval: 1, + wkst: RRule.SU, + count: 1, + until: new Date(), + tzid: "UTC", + bysetpos: [1], + bymonth: [1], + bymonthday: [2], + bynmonthday: [3], + byyearday: [20], + byweekno: [1], + byweekday: [RRule.WE], + bynweekday: [[1]], + byhour: [2], + byminute: [42], + bysecond: [21], + byeaster: 1, +}; diff --git a/src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts index 80fdbd87..523eebef 100644 --- a/src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts +++ b/src/ui/components/event-recurrence-tests/year-day-before-end-of-month.test.ts @@ -1,150 +1,157 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> dayBeforeEndOfMonth", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayBeforeEndOfMonth) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - it("should return false", () => { - const options = { - bymonthday: [1], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is an array with a negative number", () => { - it("should return true", () => { - const options = { - bymonthday: [-1], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is a positive number", () => { - it("should return false", () => { - const options = { - bymonthday: 1, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is a negative number", () => { - it("should return true", () => { - const options = { - bymonthday: -1, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is undefined", () => { - it("should return false", () => { - const options = { - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is null", () => { - it("should return false", () => { - const options = { - bymonthday: null, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is undefined", () => { - it("should return false", () => { - const options = { - bymonthday: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is null", () => { - it("should return false", () => { - const options = { - bymonthday: [1], - bymonth: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the dateStats days until end of month and month in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bymonthday).toMatchObject([-30]); - expect(result.bymonth).toMatchObject([9]); - }); - }); - - describe("filterProps", () => { - it("should return an options with the two expected properties", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(2); - expect(resultKeys).toContain("bymonthday"); - expect(resultKeys).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 19th to last day of September" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayBeforeEndOfMonth", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === + MonthYearRecurrenceType.dayBeforeEndOfMonth + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: [-1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + it("should return false", () => { + const options = { + bymonthday: 1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + it("should return true", () => { + const options = { + bymonthday: -1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = { + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats days until end of month and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([-30]); + expect(result.bymonth).toMatchObject([9]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the two expected properties", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bymonthday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 19th to last day of September"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts b/src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts index 6a937aaf..4f6399bb 100644 --- a/src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts +++ b/src/ui/components/event-recurrence-tests/year-day-before-end-of-year.test.ts @@ -1,115 +1,120 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> dayBeforeEndOfYear", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayBeforeEndOfYear) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2024-01-01")); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - it("should return false", () => { - const options = { - byyearday: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is an array with a negative number", () => { - it("should return true", () => { - const options = { - byyearday: [-1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is a positive number", () => { - it("should return false", () => { - const options = { - byyearday: 1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is a negative number", () => { - it("should return true", () => { - const options = { - byyearday: -1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is undefined", () => { - it("should return false", () => { - const options = {}; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is null", () => { - it("should return false", () => { - const options = { - byyearday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the dateStats days until end of year in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.byyearday).toMatchObject([-366]); - }); - }); - - describe("filterProps", () => { - it("should return an options with the expected property", () => { - const result = currentInfo.filterProps(defaultOptions); - - expect(Object.keys(result)).toMatchObject([ - "byyearday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2024-01-01") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 366th to last day of the year" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayBeforeEndOfYear", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === + MonthYearRecurrenceType.dayBeforeEndOfYear + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2024-01-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return false", () => { + const options = { + byyearday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return true", () => { + const options = { + byyearday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a positive number", () => { + it("should return false", () => { + const options = { + byyearday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a negative number", () => { + it("should return true", () => { + const options = { + byyearday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + byyearday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats days until end of year in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.byyearday).toMatchObject([-366]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the expected property", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject(["byyearday"]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2024-01-01") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 366th to last day of the year"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-day-of-month.test.ts b/src/ui/components/event-recurrence-tests/year-day-of-month.test.ts index c4efee26..d940b69c 100644 --- a/src/ui/components/event-recurrence-tests/year-day-of-month.test.ts +++ b/src/ui/components/event-recurrence-tests/year-day-of-month.test.ts @@ -1,149 +1,156 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> dayOfMonth", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayOfMonth) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - it("should return true", () => { - const options = { - bymonthday: [1], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is an array with a negative number", () => { - it("should return false", () => { - const options = { - bymonthday: [-1], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is a positive number", () => { - it("should return true", () => { - const options = { - bymonthday: 1, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is a negative number", () => { - it("should return false", () => { - const options = { - bymonthday: -1, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is undefined", () => { - it("should return false", () => { - const options = { - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is null", () => { - it("should return false", () => { - const options = { - bymonthday: null, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is undefined", () => { - it("should return false", () => { - const options = { - bymonthday: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is null", () => { - it("should return false", () => { - const options = { - bymonthday: [1], - bymonth: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the dateStats month day and month in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bymonthday).toMatchObject([1]); - - expect(result.bymonth).toMatchObject([9]); - }); - }); - - describe("filterProps", () => { - it("should return an options with the two expected properties", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(2); - expect(resultKeys).toContain("bymonthday"); - expect(resultKeys).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe("on September 12th"); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayOfMonth", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => info.recurrenceType === MonthYearRecurrenceType.dayOfMonth + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: [-1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + it("should return true", () => { + const options = { + bymonthday: 1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + it("should return false", () => { + const options = { + bymonthday: -1, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = { + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + bymonthday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bymonthday: [1], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats month day and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bymonthday).toMatchObject([1]); + + expect(result.bymonth).toMatchObject([9]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the two expected properties", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bymonthday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on September 12th"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-day-of-year.test.ts b/src/ui/components/event-recurrence-tests/year-day-of-year.test.ts index 497877d7..96462f47 100644 --- a/src/ui/components/event-recurrence-tests/year-day-of-year.test.ts +++ b/src/ui/components/event-recurrence-tests/year-day-of-year.test.ts @@ -1,115 +1,118 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> dayOfYear", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.dayOfYear) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-01-01")); - - describe("hasProps", () => { - describe("when day is an array with a positive number", () => { - it("should return true", () => { - const options = { - byyearday: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is an array with a negative number", () => { - it("should return false", () => { - const options = { - byyearday: [-1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is a positive number", () => { - it("should return true", () => { - const options = { - byyearday: 1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when day is a negative number", () => { - it("should return false", () => { - const options = { - byyearday: -1, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is undefined", () => { - it("should return false", () => { - const options = {}; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when day is null", () => { - it("should return false", () => { - const options = { - byyearday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the dateStats year day in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.byyearday).toMatchObject([1]); - }); - }); - - describe("filterProps", () => { - it("should return an options with the expected property", () => { - const result = currentInfo.filterProps(defaultOptions); - - expect(Object.keys(result)).toMatchObject([ - "byyearday", - ]); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2024-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 256th day of the year" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> dayOfYear", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => info.recurrenceType === MonthYearRecurrenceType.dayOfYear + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-01-01")); + + describe("hasProps", () => { + describe("when day is an array with a positive number", () => { + it("should return true", () => { + const options = { + byyearday: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is an array with a negative number", () => { + it("should return false", () => { + const options = { + byyearday: [-1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is a positive number", () => { + it("should return true", () => { + const options = { + byyearday: 1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when day is a negative number", () => { + it("should return false", () => { + const options = { + byyearday: -1, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is undefined", () => { + it("should return false", () => { + const options = {}; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when day is null", () => { + it("should return false", () => { + const options = { + byyearday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the dateStats year day in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.byyearday).toMatchObject([1]); + }); + }); + + describe("filterProps", () => { + it("should return an options with the expected property", () => { + const result = currentInfo.filterProps(defaultOptions); + + expect(Object.keys(result)).toMatchObject(["byyearday"]); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2024-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 256th day of the year"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts index c9e489cf..7afc5c3a 100644 --- a/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts +++ b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-month.test.ts @@ -1,196 +1,199 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { RRule } from "rrule"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> weekdayBeforeEndOfMonth", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayBeforeEndOfMonth) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - it("should return true", () => { - const options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is a positive number", () => { - it("should return false", () => { - const options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is a negative number", () => { - it("should return true", () => { - const options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is undefined", () => { - it("should return false", () => { - const options = { - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - it("should return false", () => { - const options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: null, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the weekdaysFromMonthEnd, weekday and month in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bysetpos).toMatchObject([ - dateStats.weekdaysFromMonthEnd * -1, - ]); - - expect(result.byweekday).toMatchObject([ - dateStats.weekday, - ]); - - expect(result.bymonth).toMatchObject([ - dateStats.month, - ]); - }); - }); - - describe("filterProps", () => { - it("should only return the three expected keys in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(3); - expect(resultKeys).toContain("bysetpos"); - expect(resultKeys).toContain("byweekday"); - expect(resultKeys).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 3rd to last Tuesday of September" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayBeforeEndOfMonth", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === + MonthYearRecurrenceType.weekdayBeforeEndOfMonth + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdaysFromMonthEnd, weekday and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdaysFromMonthEnd * -1, + ]); + + expect(result.byweekday).toMatchObject([dateStats.weekday]); + + expect(result.bymonth).toMatchObject([dateStats.month]); + }); + }); + + describe("filterProps", () => { + it("should only return the three expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(3); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 3rd to last Tuesday of September"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts index 854fbdd5..f43ca137 100644 --- a/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts +++ b/src/ui/components/event-recurrence-tests/year-weekday-before-end-of-year.test.ts @@ -1,156 +1,161 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { RRule } from "rrule"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> weekdayBeforeEndOfYear", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayBeforeEndOfYear) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - it("should return true", () => { - const options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is a positive number", () => { - it("should return false", () => { - const options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is a negative number", () => { - it("should return true", () => { - const options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is undefined", () => { - it("should return false", () => { - const options = { - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - it("should return false", () => { - const options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the weekdaysFromYearEnd and weekday in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bysetpos).toMatchObject([ - dateStats.weekdaysFromYearEnd * -1, - ]); - - expect(result.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - it("should only return the two expected keys in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(2); - expect(resultKeys).toContain("bysetpos"); - expect(resultKeys).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2024-01-01") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 53rd to last Monday of the year" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayBeforeEndOfYear", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === + MonthYearRecurrenceType.weekdayBeforeEndOfYear + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return false", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return true", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdaysFromYearEnd and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([ + dateStats.weekdaysFromYearEnd * -1, + ]); + + expect(result.byweekday).toMatchObject([dateStats.weekday]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2024-01-01") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 53rd to last Monday of the year"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts index 0c234588..29a83c1b 100644 --- a/src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts +++ b/src/ui/components/event-recurrence-tests/year-weekday-in-month.test.ts @@ -1,196 +1,196 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { RRule } from "rrule"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> weekdayInMonth", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayInMonth) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - it("should return true", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - it("should return false", () => { - const options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is a positive number", () => { - it("should return true", () => { - const options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is a negative number", () => { - it("should return false", () => { - const options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is undefined", () => { - it("should return false", () => { - const options = { - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - it("should return false", () => { - const options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: null, - bymonth: [9], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when month is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - bymonth: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the weekdayInMonth, weekday and month in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bysetpos).toMatchObject([ - dateStats.weekdayInMonth, - ]); - - expect(result.byweekday).toMatchObject([ - dateStats.weekday, - ]); - - expect(result.bymonth).toMatchObject([ - dateStats.month, - ]); - }); - }); - - describe("filterProps", () => { - it("should only return the three expected keys in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(3); - expect(resultKeys).toContain("bysetpos"); - expect(resultKeys).toContain("byweekday"); - expect(resultKeys).toContain("bymonth"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 2nd Tuesday of September" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayInMonth", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === MonthYearRecurrenceType.weekdayInMonth + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + bymonth: [9], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when month is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + bymonth: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdayInMonth, weekday and month in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([dateStats.weekdayInMonth]); + + expect(result.byweekday).toMatchObject([dateStats.weekday]); + + expect(result.bymonth).toMatchObject([dateStats.month]); + }); + }); + + describe("filterProps", () => { + it("should only return the three expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(3); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + expect(resultKeys).toContain("bymonth"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 2nd Tuesday of September"); + }); + }); + }); +}); diff --git a/src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts b/src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts index 90b2fc4a..2b60e7af 100644 --- a/src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts +++ b/src/ui/components/event-recurrence-tests/year-weekday-in-year.test.ts @@ -1,156 +1,158 @@ -import { DateTime } from "luxon"; -import { MonthYearRecurrenceType, YEAR_RECURRENCE_INFO, getDateStats } from "../event-recurrence-types"; -import { RRule } from "rrule"; -import { defaultOptions } from "./test-helpers"; - -describe("Yearly Recurrence -> weekdayInYear", () => { - const currentInfo = YEAR_RECURRENCE_INFO.find((info) => info.recurrenceType === MonthYearRecurrenceType.weekdayInYear) ?? YEAR_RECURRENCE_INFO[0]; - - const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); - - describe("hasProps", () => { - describe("when bysetpos is an array with a positive number", () => { - it("should return true", () => { - const options = { - bysetpos: [1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is an array with a negative number", () => { - it("should return false", () => { - const options = { - bysetpos: [-1], - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is a positive number", () => { - it("should return true", () => { - const options = { - bysetpos: 1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(true); - }); - }); - - describe("when bysetpos is a negative number", () => { - it("should return false", () => { - const options = { - bysetpos: -1, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is undefined", () => { - it("should return false", () => { - const options = { - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when bysetpos is null", () => { - it("should return false", () => { - const options = { - bysetpos: null, - byweekday: [RRule.MO.weekday], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is undefined", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - - describe("when weekday is null", () => { - it("should return false", () => { - const options = { - bysetpos: [1], - byweekday: null, - }; - - const result = currentInfo.hasProps(options); - - expect(result).toBe(false); - }); - }); - }); - - describe("getProps", () => { - it("should put the weekdayInYear and weekday in the options", () => { - const result = currentInfo.getProps(dateStats); - - expect(result.bysetpos).toMatchObject([ - dateStats.weekdayInYear, - ]); - - expect(result.byweekday).toMatchObject([ - dateStats.weekday, - ]); - }); - }); - - describe("filterProps", () => { - it("should only return the two expected keys in the options", () => { - const result = currentInfo.filterProps(defaultOptions); - - const resultKeys = Object.keys(result); - - expect(resultKeys).toHaveLength(2); - expect(resultKeys).toContain("bysetpos"); - expect(resultKeys).toContain("byweekday"); - }); - }); - - describe("getDisplay", () => { - describe("when called with date stats", () => { - it("should return the expected text", () => { - const currentDateStats = getDateStats( - DateTime.fromISO("2023-09-12") - ); - - const result = currentInfo.getDisplay(currentDateStats); - - expect(result).toBe( - "on the 37th Tuesday of the year" - ); - }); - }); - }); -}); +import { DateTime } from "luxon"; +import { + MonthYearRecurrenceType, + YEAR_RECURRENCE_INFO, + getDateStats, +} from "../event-recurrence-types"; +import { RRule } from "rrule"; +import { defaultOptions } from "./test-helpers"; + +describe("Yearly Recurrence -> weekdayInYear", () => { + const currentInfo = + YEAR_RECURRENCE_INFO.find( + (info) => + info.recurrenceType === MonthYearRecurrenceType.weekdayInYear + ) ?? YEAR_RECURRENCE_INFO[0]; + + const dateStats = getDateStats(DateTime.fromISO("2023-09-01")); + + describe("hasProps", () => { + describe("when bysetpos is an array with a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: [1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is an array with a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: [-1], + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is a positive number", () => { + it("should return true", () => { + const options = { + bysetpos: 1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(true); + }); + }); + + describe("when bysetpos is a negative number", () => { + it("should return false", () => { + const options = { + bysetpos: -1, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is undefined", () => { + it("should return false", () => { + const options = { + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when bysetpos is null", () => { + it("should return false", () => { + const options = { + bysetpos: null, + byweekday: [RRule.MO.weekday], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is undefined", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + + describe("when weekday is null", () => { + it("should return false", () => { + const options = { + bysetpos: [1], + byweekday: null, + }; + + const result = currentInfo.hasProps(options); + + expect(result).toBe(false); + }); + }); + }); + + describe("getProps", () => { + it("should put the weekdayInYear and weekday in the options", () => { + const result = currentInfo.getProps(dateStats); + + expect(result.bysetpos).toMatchObject([dateStats.weekdayInYear]); + + expect(result.byweekday).toMatchObject([dateStats.weekday]); + }); + }); + + describe("filterProps", () => { + it("should only return the two expected keys in the options", () => { + const result = currentInfo.filterProps(defaultOptions); + + const resultKeys = Object.keys(result); + + expect(resultKeys).toHaveLength(2); + expect(resultKeys).toContain("bysetpos"); + expect(resultKeys).toContain("byweekday"); + }); + }); + + describe("getDisplay", () => { + describe("when called with date stats", () => { + it("should return the expected text", () => { + const currentDateStats = getDateStats( + DateTime.fromISO("2023-09-12") + ); + + const result = currentInfo.getDisplay(currentDateStats); + + expect(result).toBe("on the 37th Tuesday of the year"); + }); + }); + }); +});