diff --git a/.idea/aegis.iml b/.idea/aegis.iml index 48aaa697..570565ed 100644 --- a/.idea/aegis.iml +++ b/.idea/aegis.iml @@ -3,10 +3,12 @@ + + diff --git a/.idea/runConfigurations/All_Tests__aegis_react_.xml b/.idea/runConfigurations/All_Tests__aegis_react_.xml new file mode 100644 index 00000000..0f3d1290 --- /dev/null +++ b/.idea/runConfigurations/All_Tests__aegis_react_.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/packages/core/.swcrc b/packages/core/.swcrc index 17c7b024..845a4791 100644 --- a/packages/core/.swcrc +++ b/packages/core/.swcrc @@ -4,7 +4,31 @@ "syntax": "typescript" }, "target": "es2022", - "loose": true + "assumptions": { + "arrayLikeIsIterable": true, + "constantReexports": true, + "constantSuper": true, + "enumerableModuleMeta": true, + "ignoreFunctionLength": true, + "ignoreFunctionName": true, + "ignoreToPrimitiveHint": true, + "iterableIsArray": true, + "mutableTemplateObject": true, + "noClassCalls": true, + "noDocumentAll": true, + "noIncompleteNsImportDetection": true, + "noNewArrows": true, + "objectRestNoSymbols": true, + "privateFieldsAsProperties": true, + "pureGetters": true, + "setClassMethods": true, + "setComputedProperties": true, + "setPublicClassFields": true, + "setSpreadProperties": true, + "skipForOfIteratorClosing": true, + "superIsCallableConstructor": false, + "tsEnumIsReadonly": true + } }, "module": { "type": "es6" diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index 7a2fd4f1..a2cebba2 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -23,7 +23,7 @@ export default { collectCoverageFrom: ['src/**'], // The directory where Jest should output its coverage files - coverageDirectory: "coverage", + coverageDirectory: 'coverage', // An array of regexp pattern strings used to skip coverage collection // coveragePathIgnorePatterns: [ @@ -31,7 +31,7 @@ export default { // ], // Indicates which provider should be used to instrument code for coverage - // coverageProvider: "v8", + // coverageProvider: 'v8', // A list of reporter names that Jest uses when writing coverage reports // coverageReporters: [ @@ -100,7 +100,7 @@ export default { // notifyMode: "failure-change", // A preset that is used as a base for Jest's configuration - preset: 'ts-jest', + // preset: 'ts-jest', // Run tests from one or more projects // projects: undefined, @@ -125,7 +125,7 @@ export default { // A list of paths to directories that Jest should use to search for files in roots: [ - "/tests" + '/tests' ], // Allows you to use a custom runner instead of Jest's default test runner @@ -144,7 +144,7 @@ export default { // snapshotSerializers: [], // The test environment that will be used for testing - testEnvironment: "jsdom", + testEnvironment: 'jsdom', // Options that will be passed to the testEnvironment // testEnvironmentOptions: {}, @@ -173,7 +173,9 @@ export default { // testRunner: "jest-circus/runner", // A map from regular expressions to paths to transformers - // transform: undefined, + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest'] + }, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // transformIgnorePatterns: [ diff --git a/packages/core/package.json b/packages/core/package.json index d4632bf4..55da3451 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@jujulego/aegis-core", - "version": "1.0.0-alpha.11", + "version": "1.0.0-alpha.12", "license": "MIT", "author": "Julien Capellari ", "repository": { @@ -19,13 +19,9 @@ "module": "./dist/esm/index.js", "types": "./dist/types/index.d.ts", "exports": { - "node": { - "module": "./dist/esm/index.js", - "require": "./dist/cjs/index.js" - }, "types": "./dist/types/index.d.ts", "module": "./dist/esm/index.js", - "default": "./dist/esm/index.js" + "require": "./dist/cjs/index.js" }, "scripts": { "lint": "eslint {src,tests}/**/*.*", @@ -41,6 +37,7 @@ "@jujulego/flow": "1.1.0", "@swc/cli": "0.1.57", "@swc/core": "1.2.198", + "@swc/jest": "0.2.21", "@types/gulp": "4.0.9", "@types/jest": "28.1.1", "@types/node": "16.11.39", @@ -56,6 +53,7 @@ "gulp-cli": "2.3.0", "jest": "28.1.1", "jest-environment-jsdom": "28.1.1", + "regenerator-runtime": "0.13.9", "ts-jest": "28.0.4", "ts-node": "10.8.1", "typescript": "4.7.3" diff --git a/packages/core/src/entities/list.ts b/packages/core/src/entities/list.ts index ba18492a..0f04e7c4 100644 --- a/packages/core/src/entities/list.ts +++ b/packages/core/src/entities/list.ts @@ -9,6 +9,7 @@ export class AegisList extends TypedEventTarget> { // Attributes private _ids: string[] = []; private _query?: WeakRef>; + private _cache?: WeakRef; private readonly _extractor: EntityIdExtractor; // Constructor @@ -48,6 +49,7 @@ export class AegisList extends TypedEventTarget> { query.addEventListener('update', (event) => { if (event.state.status === 'completed') { this._ids = event.state.data.map((ent) => this._extractor(ent)); + this._cache = undefined; } this.dispatchEvent(new ListUpdateEvent(this)); @@ -58,6 +60,14 @@ export class AegisList extends TypedEventTarget> { // Properties get data(): T[] { + // Use cache first + const cached = this._cache?.deref(); + + if (cached) { + return cached; + } + + // Read from store const data: T[] = []; for (const id of this._ids) { @@ -68,6 +78,8 @@ export class AegisList extends TypedEventTarget> { } } + this._cache = new WeakRef(data); + return data; } @@ -82,6 +94,7 @@ export class AegisList extends TypedEventTarget> { } this._ids = ids; + this._cache = undefined; this.dispatchEvent(new ListUpdateEvent(this)); } diff --git a/packages/core/src/stores/storage.store.ts b/packages/core/src/stores/storage.store.ts index 85c6c354..bb849c0e 100644 --- a/packages/core/src/stores/storage.store.ts +++ b/packages/core/src/stores/storage.store.ts @@ -3,6 +3,9 @@ import { StoreUpdateEvent } from './store-update.event'; // Class export class AegisStorageStore extends AegisStore { + // Attributes + private _cache = new Map>(); + // Constructor constructor(readonly storage: Storage) { super(); @@ -20,6 +23,7 @@ export class AegisStorageStore extends AegisStore { if (event.storageArea === this.storage && event.key && event.newValue) { if (!event.key.startsWith('aegis:')) return; + this._cache.delete(event.key); const [, entity, id] = event.key.split(':'); this.dispatchEvent(new StoreUpdateEvent( @@ -32,14 +36,31 @@ export class AegisStorageStore extends AegisStore { } get(entity: string, id: string): T | undefined { - const data = this.storage.getItem(this._key(entity, id)) || undefined; - return data && JSON.parse(data); + const key = this._key(entity, id); + + // Use cache first + const cached = this._cache.get(key)?.deref() as T | undefined; + + if (cached !== undefined) { + return cached; + } + + // Read from storage + const data = this.storage.getItem(key) ?? undefined; + const parsed = data === undefined ? data : JSON.parse(data) as T; + + if (typeof parsed === 'object') { + this._cache.set(key, new WeakRef(parsed as unknown as object)); + } + + return parsed; } set(entity: string, id: string, data: T): T | undefined { const old = this.get(entity, id); this.storage.setItem(this._key(entity, id), JSON.stringify(data)); + this._cache.delete(this._key(entity, id)); this.dispatchEvent(new StoreUpdateEvent(entity, id, data, old)); return old; @@ -49,6 +70,7 @@ export class AegisStorageStore extends AegisStore { const old = this.get(entity, id); this.storage.removeItem(this._key(entity, id)); + this._cache.delete(this._key(entity, id)); return old; } diff --git a/packages/core/tests/stores/storage.store.test.ts b/packages/core/tests/stores/storage.store.test.ts index 0d625a43..17ba10ae 100644 --- a/packages/core/tests/stores/storage.store.test.ts +++ b/packages/core/tests/stores/storage.store.test.ts @@ -65,6 +65,13 @@ describe('AegisStorageStore.get', () => { it('should return undefined for unknown entities', () => { expect(store.get('unknown', 'unknown')).toBeUndefined(); }); + + it('should return the same instance', () => { + store.set('test', 'get', { test: 1, success: true }); + + expect(store.get('test', 'get')).toEqual({ test: 1, success: true }); + expect(store.get('test', 'get')).toBe(store.get('test', 'get')); + }); }); describe('AegisStorageStore.set', () => { diff --git a/packages/react/.eslintrc.json b/packages/react/.eslintrc.json new file mode 100644 index 00000000..128cd29f --- /dev/null +++ b/packages/react/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "../../.eslintrc.json" + ] +} diff --git a/packages/react/.gitignore b/packages/react/.gitignore new file mode 100644 index 00000000..e3c0b53b --- /dev/null +++ b/packages/react/.gitignore @@ -0,0 +1,3 @@ +# Generated files +coverage +dist diff --git a/packages/react/.swcrc b/packages/react/.swcrc new file mode 100644 index 00000000..17c7b024 --- /dev/null +++ b/packages/react/.swcrc @@ -0,0 +1,15 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript" + }, + "target": "es2022", + "loose": true + }, + "module": { + "type": "es6" + }, + "env": { + "coreJs": "3.22" + } +} diff --git a/packages/react/LICENSE b/packages/react/LICENSE new file mode 100644 index 00000000..fb8e1a2a --- /dev/null +++ b/packages/react/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Julien Capellari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/react/README.md b/packages/react/README.md new file mode 100644 index 00000000..6e28c610 --- /dev/null +++ b/packages/react/README.md @@ -0,0 +1,7 @@ +# @jujulego/aegis-core +[![Version](https://img.shields.io/npm/v/@jujulego/aegis-core)](https://www.npmjs.com/package/@jujulego/aegis-core) +![Licence](https://img.shields.io/github/license/jujulego/aegis) +[![Bundled size](https://badgen.net/bundlephobia/minzip/@jujulego/aegis-core)](https://bundlephobia.com/package/@jujulego/aegis-core) +[![Tree shaking](https://badgen.net/bundlephobia/tree-shaking/@jujulego/aegis-core)](https://bundlephobia.com/package/@jujulego/aegis-core) + +## Description diff --git a/packages/react/gulpfile.ts b/packages/react/gulpfile.ts new file mode 100644 index 00000000..8c3d3cae --- /dev/null +++ b/packages/react/gulpfile.ts @@ -0,0 +1,44 @@ +import { flow, src, dest, swc, dts } from 'aegis-tools'; +import del from 'del'; +import gulp from 'gulp'; +import path from 'path'; + +// Config +const options = { + src: 'src/**/*.ts', + output: 'dist', + tsconfig: 'tsconfig.json', + deps: [ + '../../.pnp.*' + ] +} + +// Tasks +gulp.task('clean', () => del(options.output)); + +gulp.task('build:esm', () => flow( + src(options.src, { since: gulp.lastRun('build:esm') }), + swc({ module: { type: "es6" } }), + dest(path.join(options.output, 'esm')) +)); + +gulp.task('build:cjs', () => flow( + src(options.src, { since: gulp.lastRun('build:cjs') }), + swc({ module: { type: "commonjs" } }), + dest(path.join(options.output, 'cjs')) +)); + +gulp.task('build:types', () => flow( + src(options.src, { since: gulp.lastRun('build:types') }), + dts(options.tsconfig), + dest(path.join(options.output, 'types')) +)); + +gulp.task('build', gulp.series( + 'clean', + gulp.parallel('build:cjs', 'build:esm', 'build:types'), +)); + +gulp.task('watch', () => gulp.watch([options.src, ...options.deps], { ignoreInitial: false }, + gulp.parallel('build:cjs', 'build:esm', 'build:types'), +)); diff --git a/packages/react/jest.config.ts b/packages/react/jest.config.ts new file mode 100644 index 00000000..257535c3 --- /dev/null +++ b/packages/react/jest.config.ts @@ -0,0 +1,197 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +export default { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "C:\\Users\\julie\\AppData\\Local\\Temp\\jest", + + // Automatically clear mock calls, instances, contexts and results before every test + // clearMocks: false, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + collectCoverageFrom: ['src/**'], + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // Indicates which provider should be used to instrument code for coverage + // coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: 'ts-jest', + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + roots: [ + '/tests' + ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: 'jsdom', + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest'] + }, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "\\\\node_modules\\\\", + // "\\.pnp\\.[^\\\\]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 00000000..c86007f3 --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,72 @@ +{ + "name": "@jujulego/aegis-react", + "version": "1.0.0-alpha.1", + "license": "MIT", + "author": "Julien Capellari ", + "repository": { + "type": "git", + "url": "https://github.com/jujulego/aegis", + "directory": "packages/react" + }, + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "files": [ + "dist" + ], + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + "types": "./dist/types/index.d.ts", + "module": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + }, + "scripts": { + "lint": "eslint {src,tests}/**/*.*", + "clean": "gulp clean", + "build": "gulp build", + "watch": "gulp watch", + "test": "jest" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "regenerator-runtime": "^0.13.0" + }, + "dependencies": { + "@jujulego/aegis-core": "^1.0.0-alpha.12", + "use-sync-external-store": "^1.1.0" + }, + "devDependencies": { + "@jujulego/flow": "1.1.0", + "@swc/cli": "0.1.57", + "@swc/core": "1.2.198", + "@swc/jest": "0.2.21", + "@testing-library/react": "13.3.0", + "@types/gulp": "4.0.9", + "@types/jest": "28.1.1", + "@types/node": "16.11.39", + "@types/react": "18.0.12", + "@types/testing-library__react": "10.2.0", + "@types/use-sync-external-store": "0.0.3", + "@typescript-eslint/eslint-plugin": "5.27.1", + "@typescript-eslint/parser": "5.27.1", + "aegis-tools": "1.0.0", + "browserslist": "4.20.4", + "del": "6.1.1", + "eslint": "8.17.0", + "eslint-plugin-jest": "26.5.3", + "eslint-plugin-workspaces": "0.7.0", + "gulp": "4.0.2", + "gulp-cli": "2.3.0", + "jest": "28.1.1", + "jest-environment-jsdom": "28.1.1", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "regenerator-runtime": "0.13.9", + "ts-jest": "28.0.4", + "ts-node": "10.8.1", + "typescript": "4.7.3" + } +} diff --git a/packages/react/src/hooks/index.ts b/packages/react/src/hooks/index.ts new file mode 100644 index 00000000..ec2ffbf1 --- /dev/null +++ b/packages/react/src/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './useAegisItem'; +export * from './useAegisList'; diff --git a/packages/react/src/hooks/useAegisItem.ts b/packages/react/src/hooks/useAegisItem.ts new file mode 100644 index 00000000..056dd2f2 --- /dev/null +++ b/packages/react/src/hooks/useAegisItem.ts @@ -0,0 +1,13 @@ +import { AegisItem } from '@jujulego/aegis-core'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; + +// Hooks +export function useAegisItem(item: AegisItem) { + return useSyncExternalStore( + (cb) => { + item.addEventListener('update', cb); + return () => item.removeEventListener('update', cb); + }, + () => item.data + ); +} diff --git a/packages/react/src/hooks/useAegisList.ts b/packages/react/src/hooks/useAegisList.ts new file mode 100644 index 00000000..efd823e2 --- /dev/null +++ b/packages/react/src/hooks/useAegisList.ts @@ -0,0 +1,13 @@ +import { AegisList } from '@jujulego/aegis-core'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; + +// Hooks +export function useAegisList(list: AegisList) { + return useSyncExternalStore( + (cb) => { + list.addEventListener('update', cb); + return () => list.removeEventListener('update', cb); + }, + () => list.data + ); +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts new file mode 100644 index 00000000..755b474a --- /dev/null +++ b/packages/react/src/index.ts @@ -0,0 +1,3 @@ +export * from '@jujulego/aegis-core'; + +export * from './hooks'; diff --git a/packages/react/tests/hooks/useAegisItem.test.ts b/packages/react/tests/hooks/useAegisItem.test.ts new file mode 100644 index 00000000..9dbd8fd6 --- /dev/null +++ b/packages/react/tests/hooks/useAegisItem.test.ts @@ -0,0 +1,44 @@ +import { act, renderHook } from '@testing-library/react'; + +import { $entity, $store, useAegisItem } from '../../src'; + +// Types +interface TestEntity { + id: string; + success: boolean; +} + +// Setup +const ent = $entity('test', $store.memory(), (itm) => itm.id); + +// Tests +describe('useAegisItem', () => { + it('should return item data', () => { + const itm = ent.$entity.getItem('test-1'); + itm.data = { + id: 'test-1', + success: true + }; + + // Render + const { result } = renderHook(() => useAegisItem(itm)); + + expect(result.current).toEqual({ + id: 'test-1', + success: true + }); + + // Update + act(() => { + itm.data = { + id: 'test-1', + success: false + }; + }); + + expect(result.current).toEqual({ + id: 'test-1', + success: false + }); + }); +}); diff --git a/packages/react/tests/hooks/useAegisList.test.ts b/packages/react/tests/hooks/useAegisList.test.ts new file mode 100644 index 00000000..d0504dbe --- /dev/null +++ b/packages/react/tests/hooks/useAegisList.test.ts @@ -0,0 +1,26 @@ +import { act, renderHook } from '@testing-library/react'; + +import { $entity, $store, useAegisList } from '../../src'; + +// Setup +const ent = $entity('test', $store.memory(), (itm) => itm); + +// Tests +describe('useAegisList', () => { + it('should return item data', () => { + const lst = ent.$entity.getList('all'); + lst.data = ['test-1', 'test-2']; + + // Render + const { result } = renderHook(() => useAegisList(lst)); + + expect(result.current).toEqual(['test-1', 'test-2']); + + // Update + act(() => { + lst.data = ['test-3']; + }); + + expect(result.current).toEqual(['test-3']); + }); +}); diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json new file mode 100644 index 00000000..db89e524 --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "incremental": true, + "rootDir": ".", + "tsBuildInfoFile": "./dist/.tsbuildinfo", + } +} diff --git a/yarn.lock b/yarn.lock index e3751bc4..b4b43aae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,7 +15,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.7": version: 7.16.7 resolution: "@babel/code-frame@npm:7.16.7" dependencies: @@ -345,6 +345,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.12.5": + version: 7.18.3 + resolution: "@babel/runtime@npm:7.18.3" + dependencies: + regenerator-runtime: ^0.13.4 + checksum: db8526226aa02cfa35a5a7ac1a34b5f303c62a1f000c7db48cb06c6290e616483e5036ab3c4e7a84d0f3be6d4e2148d5fe5cec9564bf955f505c3e764b83d7f1 + languageName: node + linkType: hard + "@babel/template@npm:^7.16.7, @babel/template@npm:^7.3.3": version: 7.16.7 resolution: "@babel/template@npm:7.16.7" @@ -559,6 +568,15 @@ __metadata: languageName: node linkType: hard +"@jest/create-cache-key-function@npm:^27.4.2": + version: 27.5.1 + resolution: "@jest/create-cache-key-function@npm:27.5.1" + dependencies: + "@jest/types": ^27.5.1 + checksum: a6c3a8c769aca6f66f5dc80f1c77e66980b4f213a6b2a15a92ba3595f032848a1261c06c9c798dcf2b672b1ffbefad5085af89d130548741c85ddbe0cf4284e7 + languageName: node + linkType: hard + "@jest/environment@npm:^28.1.1": version: 28.1.1 resolution: "@jest/environment@npm:28.1.1" @@ -720,6 +738,19 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:^27.5.1": + version: 27.5.1 + resolution: "@jest/types@npm:27.5.1" + dependencies: + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^16.0.0 + chalk: ^4.0.0 + checksum: d1f43cc946d87543ddd79d49547aab2399481d34025d5c5f2025d3d99c573e1d9832fa83cef25e9d9b07a8583500229d15bbb07b8e233d127d911d133e2f14b1 + languageName: node + linkType: hard + "@jest/types@npm:^28.1.0": version: 28.1.0 resolution: "@jest/types@npm:28.1.0" @@ -809,16 +840,54 @@ __metadata: languageName: node linkType: hard -"@jujulego/aegis-core@workspace:packages/core": +"@jujulego/aegis-core@^1.0.0-alpha.12, @jujulego/aegis-core@workspace:packages/core": version: 0.0.0-use.local resolution: "@jujulego/aegis-core@workspace:packages/core" dependencies: "@jujulego/flow": 1.1.0 "@swc/cli": 0.1.57 "@swc/core": 1.2.198 + "@swc/jest": 0.2.21 + "@types/gulp": 4.0.9 + "@types/jest": 28.1.1 + "@types/node": 16.11.39 + "@typescript-eslint/eslint-plugin": 5.27.1 + "@typescript-eslint/parser": 5.27.1 + aegis-tools: 1.0.0 + browserslist: 4.20.4 + del: 6.1.1 + eslint: 8.17.0 + eslint-plugin-jest: 26.5.3 + eslint-plugin-workspaces: 0.7.0 + gulp: 4.0.2 + gulp-cli: 2.3.0 + jest: 28.1.1 + jest-environment-jsdom: 28.1.1 + regenerator-runtime: 0.13.9 + ts-jest: 28.0.4 + ts-node: 10.8.1 + typescript: 4.7.3 + peerDependencies: + regenerator-runtime: ^0.13.0 + languageName: unknown + linkType: soft + +"@jujulego/aegis-react@workspace:packages/react": + version: 0.0.0-use.local + resolution: "@jujulego/aegis-react@workspace:packages/react" + dependencies: + "@jujulego/aegis-core": ^1.0.0-alpha.12 + "@jujulego/flow": 1.1.0 + "@swc/cli": 0.1.57 + "@swc/core": 1.2.198 + "@swc/jest": 0.2.21 + "@testing-library/react": 13.3.0 "@types/gulp": 4.0.9 "@types/jest": 28.1.1 "@types/node": 16.11.39 + "@types/react": 18.0.12 + "@types/testing-library__react": 10.2.0 + "@types/use-sync-external-store": 0.0.3 "@typescript-eslint/eslint-plugin": 5.27.1 "@typescript-eslint/parser": 5.27.1 aegis-tools: 1.0.0 @@ -831,10 +900,15 @@ __metadata: gulp-cli: 2.3.0 jest: 28.1.1 jest-environment-jsdom: 28.1.1 + react: ^18.1.0 + react-dom: ^18.1.0 + regenerator-runtime: 0.13.9 ts-jest: 28.0.4 ts-node: 10.8.1 typescript: 4.7.3 + use-sync-external-store: ^1.1.0 peerDependencies: + react: ^17.0.0 || ^18.0.0 regenerator-runtime: ^0.13.0 languageName: unknown linkType: soft @@ -1149,6 +1223,47 @@ __metadata: languageName: node linkType: hard +"@swc/jest@npm:0.2.21": + version: 0.2.21 + resolution: "@swc/jest@npm:0.2.21" + dependencies: + "@jest/create-cache-key-function": ^27.4.2 + peerDependencies: + "@swc/core": "*" + checksum: 8208032fc7ccc26a9bcae744b7b7dc2d3ea313af1ea049e95dfe3c8b6abd708c6f8fd0a2cce45315e7e5627044f6c3e08363b89eb958df7d8a8693a2e45edecb + languageName: node + linkType: hard + +"@testing-library/dom@npm:^8.5.0": + version: 8.13.0 + resolution: "@testing-library/dom@npm:8.13.0" + dependencies: + "@babel/code-frame": ^7.10.4 + "@babel/runtime": ^7.12.5 + "@types/aria-query": ^4.2.0 + aria-query: ^5.0.0 + chalk: ^4.1.0 + dom-accessibility-api: ^0.5.9 + lz-string: ^1.4.4 + pretty-format: ^27.0.2 + checksum: 880f1872b9949800d4444e3bdbd03df86d6f41ec7c27136dff1da29e87d2df2d7ee904afcdf895ffce351c25bd12119117eae023354d50e707ad56d43b2ed3ed + languageName: node + linkType: hard + +"@testing-library/react@npm:*, @testing-library/react@npm:13.3.0": + version: 13.3.0 + resolution: "@testing-library/react@npm:13.3.0" + dependencies: + "@babel/runtime": ^7.12.5 + "@testing-library/dom": ^8.5.0 + "@types/react-dom": ^18.0.0 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 98fd8616a7cae0ecfcbe97b5b3c5b91fbafccf449c04875395ccc0e3f0b139e53b3261b9536ec2169a5e2883a1be2098907209064061fe0c2ff21dfbc785dd40 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -1184,6 +1299,13 @@ __metadata: languageName: node linkType: hard +"@types/aria-query@npm:^4.2.0": + version: 4.2.2 + resolution: "@types/aria-query@npm:4.2.2" + checksum: 6f2ce11d91e2d665f3873258db19da752d91d85d3679eb5efcdf9c711d14492287e1e4eb52613b28e60375841a9e428594e745b68436c963d8bad4bf72188df3 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.1.14": version: 7.1.19 resolution: "@types/babel__core@npm:7.1.19" @@ -1370,6 +1492,40 @@ __metadata: languageName: node linkType: hard +"@types/prop-types@npm:*": + version: 15.7.5 + resolution: "@types/prop-types@npm:15.7.5" + checksum: 5b43b8b15415e1f298243165f1d44390403bb2bd42e662bca3b5b5633fdd39c938e91b7fce3a9483699db0f7a715d08cef220c121f723a634972fdf596aec980 + languageName: node + linkType: hard + +"@types/react-dom@npm:^18.0.0": + version: 18.0.5 + resolution: "@types/react-dom@npm:18.0.5" + dependencies: + "@types/react": "*" + checksum: cd48b81950f499b52a3f0c08261f00046f9b7c96699fa249c9664e257e820daf6ecac815cd1028cebc9d105094adc39d047d1efd79214394b8b2d515574c0787 + languageName: node + linkType: hard + +"@types/react@npm:*, @types/react@npm:18.0.12": + version: 18.0.12 + resolution: "@types/react@npm:18.0.12" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: 526ea13b3adf7fe4b475e55b7426510a7861ef2910664a9014ef42cba0c699d5167dc378eb161e2ec26c07a3b6fde9b6bdcbbb6f4b5580612246bc289395ef03 + languageName: node + linkType: hard + +"@types/scheduler@npm:*": + version: 0.16.2 + resolution: "@types/scheduler@npm:0.16.2" + checksum: b6b4dcfeae6deba2e06a70941860fb1435730576d3689225a421280b7742318d1548b3d22c1f66ab68e414f346a9542f29240bc955b6332c5b11e561077583bc + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.1 resolution: "@types/stack-utils@npm:2.0.1" @@ -1377,6 +1533,15 @@ __metadata: languageName: node linkType: hard +"@types/testing-library__react@npm:10.2.0": + version: 10.2.0 + resolution: "@types/testing-library__react@npm:10.2.0" + dependencies: + "@testing-library/react": "*" + checksum: 85f4db1aee846a86c088e71cbc5538374996b180adcab19d15c4e0a09fe294d71e587a7547b35fb3542519c2415d8e2ee92af579b2b583a8830f56d6d6d76b79 + languageName: node + linkType: hard + "@types/tough-cookie@npm:*": version: 4.0.2 resolution: "@types/tough-cookie@npm:4.0.2" @@ -1402,6 +1567,13 @@ __metadata: languageName: node linkType: hard +"@types/use-sync-external-store@npm:0.0.3": + version: 0.0.3 + resolution: "@types/use-sync-external-store@npm:0.0.3" + checksum: 161ddb8eec5dbe7279ac971531217e9af6b99f7783213566d2b502e2e2378ea19cf5e5ea4595039d730aa79d3d35c6567d48599f69773a02ffcff1776ec2a44e + languageName: node + linkType: hard + "@types/vinyl-fs@npm:*": version: 2.4.12 resolution: "@types/vinyl-fs@npm:2.4.12" @@ -1430,6 +1602,15 @@ __metadata: languageName: node linkType: hard +"@types/yargs@npm:^16.0.0": + version: 16.0.4 + resolution: "@types/yargs@npm:16.0.4" + dependencies: + "@types/yargs-parser": "*" + checksum: caa21d2c957592fe2184a8368c8cbe5a82a6c2e2f2893722e489f842dc5963293d2f3120bc06fe3933d60a3a0d1e2eb269649fd6b1947fe1820f8841ba611dd9 + languageName: node + linkType: hard + "@types/yargs@npm:^17.0.8": version: 17.0.10 resolution: "@types/yargs@npm:17.0.10" @@ -1920,6 +2101,13 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:^5.0.0": + version: 5.0.0 + resolution: "aria-query@npm:5.0.0" + checksum: c41f98866c5a304561ee8cae55856711cddad6f3f85d8cb43cc5f79667078d9b8979ce32d244c1ff364e6463a4d0b6865804a33ccc717fed701b281cf7dc6296 + languageName: node + linkType: hard + "arr-diff@npm:^4.0.0": version: 4.0.0 resolution: "arr-diff@npm:4.0.0" @@ -2908,6 +3096,13 @@ __metadata: languageName: node linkType: hard +"csstype@npm:^3.0.2": + version: 3.1.0 + resolution: "csstype@npm:3.1.0" + checksum: 644e986cefab86525f0b674a06889cfdbb1f117e5b7d1ce0fc55b0423ecc58807a1ea42ecc75c4f18999d14fc42d1d255f84662a45003a52bb5840e977eb2ffd + languageName: node + linkType: hard + "d@npm:1, d@npm:^1.0.1": version: 1.0.1 resolution: "d@npm:1.0.1" @@ -3181,6 +3376,13 @@ __metadata: languageName: node linkType: hard +"dom-accessibility-api@npm:^0.5.9": + version: 0.5.14 + resolution: "dom-accessibility-api@npm:0.5.14" + checksum: 782c813f75a09ba6735ef03b5e1624406a3829444ae49d5bdedd272a49d437ae3354f53e02ffc8c9fd9165880250f41546538f27461f839dd4ea1234e77e8d5e + languageName: node + linkType: hard + "domexception@npm:^4.0.0": version: 4.0.0 resolution: "domexception@npm:4.0.0" @@ -5552,7 +5754,7 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^4.0.0": +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 8a95213a5a77deb6cbe94d86340e8d9ace2b93bc367790b260101d2f36a2eaf4e4e22d9fa9cf459b38af3a32fb4190e638024cf82ec95ef708680e405ea7cc78 @@ -5886,6 +6088,17 @@ __metadata: languageName: node linkType: hard +"loose-envify@npm:^1.1.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: ^3.0.0 || ^4.0.0 + bin: + loose-envify: cli.js + checksum: 6517e24e0cad87ec9888f500c5b5947032cdfe6ef65e1c1936a0c48a524b81e65542c9c3edc91c97d5bddc806ee2a985dbc79be89215d613b1de5db6d1cfe6f4 + languageName: node + linkType: hard + "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -5911,6 +6124,15 @@ __metadata: languageName: node linkType: hard +"lz-string@npm:^1.4.4": + version: 1.4.4 + resolution: "lz-string@npm:1.4.4" + bin: + lz-string: bin/bin.js + checksum: 54e31238a61a84d8f664d9860a9fba7310c5b97a52c444f80543069bc084815eff40b8d4474ae1d93992fdf6c252dca37cf27f6adbeb4dbc3df2f3ac773d0e61 + languageName: node + linkType: hard + "make-dir@npm:^3.0.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -6888,7 +7110,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^27.0.0, pretty-format@npm:^27.5.1": +"pretty-format@npm:^27.0.0, pretty-format@npm:^27.0.2, pretty-format@npm:^27.5.1": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" dependencies: @@ -6994,6 +7216,18 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:^18.1.0": + version: 18.1.0 + resolution: "react-dom@npm:18.1.0" + dependencies: + loose-envify: ^1.1.0 + scheduler: ^0.22.0 + peerDependencies: + react: ^18.1.0 + checksum: bb0d48eeb0b297c79c2a03978baa29f5b3ff7ba3d070b21e34c9af1a6e7fdf0ca8b8d73e41f9214d91ad40eeb6d1f3559f884cbbc338713374a51320637c23df + languageName: node + linkType: hard + "react-is@npm:^17.0.1": version: 17.0.2 resolution: "react-is@npm:17.0.2" @@ -7008,6 +7242,15 @@ __metadata: languageName: node linkType: hard +"react@npm:^18.1.0": + version: 18.1.0 + resolution: "react@npm:18.1.0" + dependencies: + loose-envify: ^1.1.0 + checksum: 5bb296b561b43ef2220395da4faac86c14a087c8c80e1a7598a5740f01ee605c11eaf249985c1e2000971c4cd32ccb46d40f00479bbd9fb6b1c7cf857393b7d4 + languageName: node + linkType: hard + "read-pkg-up@npm:^1.0.1": version: 1.0.1 resolution: "read-pkg-up@npm:1.0.1" @@ -7084,6 +7327,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:0.13.9, regenerator-runtime@npm:^0.13.4": + version: 0.13.9 + resolution: "regenerator-runtime@npm:0.13.9" + checksum: 65ed455fe5afd799e2897baf691ca21c2772e1a969d19bb0c4695757c2d96249eb74ee3553ea34a91062b2a676beedf630b4c1551cc6299afb937be1426ec55e + languageName: node + linkType: hard + "regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": version: 1.0.2 resolution: "regex-not@npm:1.0.2" @@ -7361,6 +7611,15 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.22.0": + version: 0.22.0 + resolution: "scheduler@npm:0.22.0" + dependencies: + loose-envify: ^1.1.0 + checksum: a8ef5cab769c020cd6382ad9ecc3f72dbde56a50a36639b3a42ad9c11f7724f03700bcad373044059b8067d4a6365154dc7c0ca8027ef20ff4900cf58a0fc2c5 + languageName: node + linkType: hard + "semver-greatest-satisfied-range@npm:^1.1.0": version: 1.1.0 resolution: "semver-greatest-satisfied-range@npm:1.1.0" @@ -8382,6 +8641,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.1.0": + version: 1.1.0 + resolution: "use-sync-external-store@npm:1.1.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 8993a0b642f91d7fcdbb02b7b3ac984bd3af4769686f38291fe7fcfe73dfb73d6c64d20dfb7e5e7fbf5a6da8f5392d6f8e5b00c243a04975595946e82c02b883 + languageName: node + linkType: hard + "use@npm:^3.1.0": version: 3.1.1 resolution: "use@npm:3.1.1"