anu-verzum 1.21.6 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -6
- package/dist/core/components/AnulyticsProvider.d.ts +3 -0
- package/dist/core/components/AnulyticsProvider.js +66 -58
- package/dist/core/components/History.d.ts +3 -0
- package/dist/core/components/History.js +10 -2
- package/dist/core/components/Intl.d.ts +3 -0
- package/dist/core/components/Intl.js +13 -2
- package/dist/core/elements.d.ts +1 -1
- package/dist/core/reconciler.d.ts +5 -0
- package/dist/core/reconciler.js +41 -2
- package/dist/testing/__tests__/smoke.test.d.ts +1 -0
- package/dist/testing/__tests__/smoke.test.js +180 -0
- package/dist/testing/act.d.ts +4 -0
- package/dist/testing/act.js +42 -0
- package/dist/testing/cleanup.d.ts +3 -0
- package/dist/testing/cleanup.js +32 -0
- package/dist/testing/events/fireEvent.d.ts +17 -0
- package/dist/testing/events/fireEvent.js +45 -0
- package/dist/testing/events/userEvent.d.ts +12 -0
- package/dist/testing/events/userEvent.js +67 -0
- package/dist/testing/globals.d.js +1 -0
- package/dist/testing/index.d.ts +9 -0
- package/dist/testing/index.js +109 -0
- package/dist/testing/queries/byAltText.d.ts +2 -0
- package/dist/testing/queries/byAltText.js +15 -0
- package/dist/testing/queries/byLabelText.d.ts +2 -0
- package/dist/testing/queries/byLabelText.js +47 -0
- package/dist/testing/queries/byPlaceholderText.d.ts +2 -0
- package/dist/testing/queries/byPlaceholderText.js +15 -0
- package/dist/testing/queries/byRole.d.ts +2 -0
- package/dist/testing/queries/byRole.js +73 -0
- package/dist/testing/queries/byTestId.d.ts +2 -0
- package/dist/testing/queries/byTestId.js +11 -0
- package/dist/testing/queries/byText.d.ts +2 -0
- package/dist/testing/queries/byText.js +27 -0
- package/dist/testing/queries/byTitle.d.ts +2 -0
- package/dist/testing/queries/byTitle.js +15 -0
- package/dist/testing/queries/index.d.ts +2 -0
- package/dist/testing/queries/index.js +43 -0
- package/dist/testing/queries/queryBuilder.d.ts +2 -0
- package/dist/testing/queries/queryBuilder.js +36 -0
- package/dist/testing/render.d.ts +3 -0
- package/dist/testing/render.js +46 -0
- package/dist/testing/types.d.ts +59 -0
- package/dist/testing/types.js +5 -0
- package/dist/testing/waitFor.d.ts +3 -0
- package/dist/testing/waitFor.js +41 -0
- package/dist/testing/wrappers.d.ts +17 -0
- package/dist/testing/wrappers.js +46 -0
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<h3>@author: <strong>Anubis-programmer</strong></h3>
|
|
6
6
|
<h3>@license: <strong>MIT</strong></h3>
|
|
7
|
-
<h3>@version: <strong>1.
|
|
7
|
+
<h3>@version: <strong>1.22.0</strong></h3>
|
|
8
8
|
|
|
9
9
|
<br>
|
|
10
10
|
|
|
@@ -17,6 +17,7 @@ A lightweight React-inspired UI library for building component-based web applica
|
|
|
17
17
|
- Client-side routing over the History API
|
|
18
18
|
- Context API, i18n (Intl), feature flags, and built-in event analytics (Anulytics)
|
|
19
19
|
- Ships with TypeScript declaration files — no `@types` package needed
|
|
20
|
+
- Built-in testing companion — **Anu Testing Library (ATL)** shipped as `anu-verzum/testing`
|
|
20
21
|
|
|
21
22
|
<br>
|
|
22
23
|
<hr>
|
|
@@ -143,6 +144,18 @@ Create `tsconfig.json`:
|
|
|
143
144
|
| `moduleResolution` | `"bundler"` | Correct setting for Webpack/Babel projects |
|
|
144
145
|
| `target` | `"ES2018"` | Because Babel handles compilation (`noEmit: true`), `target` only controls which TypeScript built-in type definitions are available — it does not affect emitted code. ES2018 is the minimum required to include `Promise.prototype.finally` on values returned by `Anu.ServerAPI` methods. |
|
|
145
146
|
|
|
147
|
+
#### Typing `process.env`
|
|
148
|
+
|
|
149
|
+
TypeScript does not know about `process` in a browser project by default — it is a Node.js global. If you reference `process.env.SOME_VAR` in your source (for example, to pass a value injected by webpack `DefinePlugin`), add a declaration file so the type checker can resolve it without pulling in the full Node.js type surface:
|
|
150
|
+
|
|
151
|
+
Create `src/env.d.ts`:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
declare const process: { env: Record<string, string | undefined> };
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This is enough for any `process.env.*` access. If your project already depends on `@types/node` for other reasons, you can skip the declaration file and add `"types": ["node"]` to `compilerOptions` in `tsconfig.json` instead — but prefer the declaration file for a pure browser project to avoid Node-specific type collisions (e.g. `setTimeout` return type, `Buffer`, etc.).
|
|
158
|
+
|
|
146
159
|
Compilation and type checking are intentionally separate — `npm start` and `npm run build` succeed regardless of type errors. Run `npx tsc --noEmit` during development to catch type issues without blocking the build.
|
|
147
160
|
|
|
148
161
|
#### Exported types
|
|
@@ -176,13 +189,40 @@ The following types are exported from `anu-verzum` for use in consumer projects:
|
|
|
176
189
|
#### Library development scripts
|
|
177
190
|
|
|
178
191
|
```bash
|
|
179
|
-
npm run clean
|
|
180
|
-
npm run build
|
|
181
|
-
npm run typecheck
|
|
182
|
-
npm run lint
|
|
183
|
-
npm run format
|
|
192
|
+
npm run clean # Delete dist/ entirely
|
|
193
|
+
npm run build # Clean, compile TypeScript sources to dist/, and emit .d.ts files
|
|
194
|
+
npm run typecheck # Type-check without emitting any output
|
|
195
|
+
npm run lint # Run ESLint on all source files
|
|
196
|
+
npm run format # Format all source files with Prettier
|
|
197
|
+
npm test # Run the Anu Testing Library test suite with Jest
|
|
198
|
+
npm run test:watch # Run Jest in interactive watch mode
|
|
199
|
+
npm run test:coverage # Run Jest and generate a coverage report
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
<br>
|
|
203
|
+
<hr>
|
|
204
|
+
|
|
205
|
+
<h2 id="testing">Testing</h2>
|
|
206
|
+
|
|
207
|
+
ANUVerzum ships a built-in testing library — **Anu Testing Library (ATL)** — importable as `anu-verzum/testing`. It mirrors the philosophy of Testing Library: query the DOM the way a user would (by role, text, label), fire events, and assert on real output — no component internals, no custom matchers.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import Anu from 'anu-verzum';
|
|
211
|
+
import { render, fireEvent } from 'anu-verzum/testing';
|
|
212
|
+
|
|
213
|
+
const { getByText, getByRole } = render(<Counter />);
|
|
214
|
+
fireEvent.click(getByRole('button'));
|
|
215
|
+
expect(getByText('Count: 1')).toBeDefined();
|
|
184
216
|
```
|
|
185
217
|
|
|
218
|
+
If TypeScript reports that `describe`, `test`, or `expect` are not found, your `tsconfig.json` likely has an explicit `"types"` list. Add `"jest"` to it — and `"node"` if tests reference `process.env`:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{ "compilerOptions": { "types": ["node", "jest"] } }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
See **[USERS_MANUAL.md — Testing](./documentation/USERS_MANUAL.md#testing)** for the full API reference and usage guide.
|
|
225
|
+
|
|
186
226
|
<br>
|
|
187
227
|
<hr>
|
|
188
228
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.trackStateChange = exports.trackRouteChange = exports.trackEvent = exports.default = void 0;
|
|
6
|
+
exports.trackStateChange = exports.trackRouteChange = exports.trackEvent = exports.default = exports.__testing = void 0;
|
|
7
7
|
var _Component = require("./Component");
|
|
8
8
|
var _serverApi = _interopRequireDefault(require("../../server-api/server-api"));
|
|
9
9
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -15,30 +15,42 @@ const EventTypes = {
|
|
|
15
15
|
PAGE_LEAVE: 'pageLeave'
|
|
16
16
|
};
|
|
17
17
|
const AnulyticsState = (() => {
|
|
18
|
-
const
|
|
18
|
+
const startDate = new Date().getTime();
|
|
19
19
|
let _anulyticsInstanceExist = false;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
20
|
+
let _analyticsUrl = '';
|
|
21
|
+
let _onSuccess = () => {};
|
|
22
|
+
let _onFail = () => {};
|
|
23
|
+
let _user = {};
|
|
24
|
+
const send = (event, extra) => {
|
|
25
|
+
_serverApi.default.post(_analyticsUrl, {
|
|
26
|
+
startDate,
|
|
27
|
+
user: _user,
|
|
28
|
+
...event,
|
|
29
|
+
...(extra || {})
|
|
30
|
+
}).then(({
|
|
31
|
+
response
|
|
32
|
+
}) => _onSuccess(response)).catch(({
|
|
33
|
+
status
|
|
34
|
+
}) => _onFail(status));
|
|
34
35
|
};
|
|
35
36
|
return {
|
|
36
|
-
getAnulyticsInstanceExist,
|
|
37
|
-
setAnulyticsInstanceExist
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
getAnulyticsInstanceExist: () => _anulyticsInstanceExist,
|
|
38
|
+
setAnulyticsInstanceExist: instanceExist => {
|
|
39
|
+
_anulyticsInstanceExist = instanceExist;
|
|
40
|
+
},
|
|
41
|
+
setConfig: (url, onSuccess, onFail) => {
|
|
42
|
+
_analyticsUrl = url;
|
|
43
|
+
_onSuccess = onSuccess;
|
|
44
|
+
_onFail = onFail;
|
|
45
|
+
},
|
|
46
|
+
setUser: user => {
|
|
47
|
+
_user = user || {};
|
|
48
|
+
},
|
|
49
|
+
getStartDate: () => startDate,
|
|
50
|
+
sendEvent: (key, val, extra) => {
|
|
51
|
+
send({
|
|
40
52
|
[key]: val
|
|
41
|
-
});
|
|
53
|
+
}, extra);
|
|
42
54
|
},
|
|
43
55
|
trackEvent: ({
|
|
44
56
|
type,
|
|
@@ -55,7 +67,7 @@ const AnulyticsState = (() => {
|
|
|
55
67
|
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
|
|
56
68
|
const scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
|
|
57
69
|
const props = typeof rawProps === 'object' && !Array.isArray(rawProps) && rawProps !== null ? rawProps : null;
|
|
58
|
-
|
|
70
|
+
send({
|
|
59
71
|
[EventTypes.USER_ACTION]: {
|
|
60
72
|
eventType: type,
|
|
61
73
|
timestamp: new Date().getTime(),
|
|
@@ -73,11 +85,10 @@ const AnulyticsState = (() => {
|
|
|
73
85
|
props
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
|
-
};
|
|
77
|
-
_anulytics.events.push(event);
|
|
88
|
+
});
|
|
78
89
|
},
|
|
79
90
|
trackStateChange: (prevState, action, nextState) => {
|
|
80
|
-
|
|
91
|
+
send({
|
|
81
92
|
[EventTypes.STATE_CHANGE]: {
|
|
82
93
|
eventType: action['type'],
|
|
83
94
|
timestamp: new Date().getTime(),
|
|
@@ -88,13 +99,8 @@ const AnulyticsState = (() => {
|
|
|
88
99
|
nextState
|
|
89
100
|
}
|
|
90
101
|
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
setUser: user => {
|
|
95
|
-
_anulytics.user = user || {};
|
|
96
|
-
},
|
|
97
|
-
getAnulyticsData: () => _anulytics
|
|
102
|
+
});
|
|
103
|
+
}
|
|
98
104
|
};
|
|
99
105
|
})();
|
|
100
106
|
const _isBot = !!(window.phantom || window._phantom || window.__nightmare || window.navigator.webdriver || window.Cypress);
|
|
@@ -122,26 +128,34 @@ exports.trackStateChange = trackStateChange;
|
|
|
122
128
|
const trackRouteChange = path => {
|
|
123
129
|
if (AnulyticsState.getAnulyticsInstanceExist()) {
|
|
124
130
|
const url = path || window.location.pathname;
|
|
125
|
-
|
|
131
|
+
AnulyticsState.sendEvent(EventTypes.NAVIGATION, {
|
|
126
132
|
eventType: url,
|
|
127
133
|
timestamp: new Date().getTime(),
|
|
128
134
|
properties: {}
|
|
129
|
-
};
|
|
130
|
-
AnulyticsState.addEvent(EventTypes.NAVIGATION, event);
|
|
135
|
+
});
|
|
131
136
|
}
|
|
132
137
|
};
|
|
133
138
|
exports.trackRouteChange = trackRouteChange;
|
|
134
139
|
class AnulyticsProvider extends _Component.Component {
|
|
135
140
|
constructor(props) {
|
|
136
141
|
super(props);
|
|
137
|
-
AnulyticsState.setAnulyticsInstanceExist(true);
|
|
138
142
|
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
|
|
139
143
|
}
|
|
140
144
|
componentDidMount() {
|
|
141
145
|
const {
|
|
142
|
-
|
|
146
|
+
analyticsUrl,
|
|
147
|
+
userData,
|
|
148
|
+
onSuccess,
|
|
149
|
+
onFail
|
|
143
150
|
} = this.props;
|
|
151
|
+
AnulyticsState.setConfig(analyticsUrl, onSuccess, onFail);
|
|
144
152
|
AnulyticsState.setUser(userData || null);
|
|
153
|
+
AnulyticsState.setAnulyticsInstanceExist(true);
|
|
154
|
+
AnulyticsState.sendEvent(EventTypes.INITIALIZATION, {
|
|
155
|
+
eventType: window.location.pathname,
|
|
156
|
+
timestamp: AnulyticsState.getStartDate(),
|
|
157
|
+
properties: {}
|
|
158
|
+
});
|
|
145
159
|
document.addEventListener('visibilitychange', this.handleVisibilityChange, {
|
|
146
160
|
passive: true
|
|
147
161
|
});
|
|
@@ -151,23 +165,12 @@ class AnulyticsProvider extends _Component.Component {
|
|
|
151
165
|
AnulyticsState.setAnulyticsInstanceExist(false);
|
|
152
166
|
}
|
|
153
167
|
handleVisibilityChange() {
|
|
154
|
-
const {
|
|
155
|
-
analyticsUrl,
|
|
156
|
-
onSuccess,
|
|
157
|
-
onFail
|
|
158
|
-
} = this.props;
|
|
159
168
|
if (_isBot) {
|
|
160
169
|
return;
|
|
161
170
|
}
|
|
162
171
|
if (document.visibilityState === 'hidden') {
|
|
163
172
|
const url = window.location.pathname;
|
|
164
|
-
const event = {
|
|
165
|
-
eventType: url,
|
|
166
|
-
timestamp: new Date().getTime(),
|
|
167
|
-
properties: {}
|
|
168
|
-
};
|
|
169
173
|
let ua;
|
|
170
|
-
AnulyticsState.addEvent(EventTypes.PAGE_LEAVE, event);
|
|
171
174
|
if (window.navigator.userAgentData) {
|
|
172
175
|
const {
|
|
173
176
|
brands,
|
|
@@ -186,21 +189,18 @@ class AnulyticsProvider extends _Component.Component {
|
|
|
186
189
|
platform: ''
|
|
187
190
|
};
|
|
188
191
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
AnulyticsState.sendEvent(EventTypes.PAGE_LEAVE, {
|
|
193
|
+
eventType: url,
|
|
194
|
+
timestamp: new Date().getTime(),
|
|
195
|
+
properties: {}
|
|
196
|
+
}, {
|
|
192
197
|
system: {
|
|
193
198
|
referrer: document.referrer || null,
|
|
194
199
|
innerWidth: window.innerWidth,
|
|
195
200
|
isMobileAppInstalled: _isInstalled(),
|
|
196
201
|
userAgentData: ua
|
|
197
202
|
}
|
|
198
|
-
};
|
|
199
|
-
_serverApi.default.post(analyticsUrl, data).then(({
|
|
200
|
-
response
|
|
201
|
-
}) => onSuccess(response)).catch(({
|
|
202
|
-
status
|
|
203
|
-
}) => onFail(status));
|
|
203
|
+
});
|
|
204
204
|
} else {
|
|
205
205
|
this.setState();
|
|
206
206
|
}
|
|
@@ -218,4 +218,12 @@ class AnulyticsProvider extends _Component.Component {
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
var _default = exports.default = AnulyticsProvider;
|
|
221
|
+
var _default = exports.default = AnulyticsProvider;
|
|
222
|
+
const __testing = exports.__testing = {
|
|
223
|
+
reset() {
|
|
224
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
AnulyticsState.setAnulyticsInstanceExist(false);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.goTo = exports.default = void 0;
|
|
6
|
+
exports.goTo = exports.default = exports.__testing = void 0;
|
|
7
7
|
var _elements = require("../elements");
|
|
8
8
|
var _Component = require("./Component");
|
|
9
9
|
var _AnulyticsProvider = require("./AnulyticsProvider");
|
|
@@ -210,4 +210,12 @@ const History = {
|
|
|
210
210
|
getUrlParams,
|
|
211
211
|
getAllUrlParamNames
|
|
212
212
|
};
|
|
213
|
-
var _default = exports.default = History;
|
|
213
|
+
var _default = exports.default = History;
|
|
214
|
+
const __testing = exports.__testing = {
|
|
215
|
+
reset() {
|
|
216
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
instances.length = 0;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = void 0;
|
|
6
|
+
exports.default = exports.__testing = void 0;
|
|
7
7
|
var _elements = require("../elements");
|
|
8
8
|
var _Context = require("./Context");
|
|
9
9
|
const _Intl = (0, _Context.createContext)({});
|
|
@@ -168,4 +168,15 @@ const Intl = {
|
|
|
168
168
|
formatMessage,
|
|
169
169
|
Provider: IntlProvider
|
|
170
170
|
};
|
|
171
|
-
var _default = exports.default = Intl;
|
|
171
|
+
var _default = exports.default = Intl;
|
|
172
|
+
const __testing = exports.__testing = {
|
|
173
|
+
reset() {
|
|
174
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
__messagesContext = {
|
|
178
|
+
locale: undefined,
|
|
179
|
+
messages: {}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
};
|
package/dist/core/elements.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type Ref<T> = {
|
|
|
12
12
|
};
|
|
13
13
|
export type FunctionComponent<P extends Props = Props> = (props: P) => AnuElement<any, any> | AnuElement<any, any>[] | string | number | boolean | null | undefined;
|
|
14
14
|
export type ComponentConstructor<P extends Props = Props> = new (props: P, context?: Record<string, any>) => any;
|
|
15
|
-
export type ElementType = string | FunctionComponent | ComponentConstructor
|
|
15
|
+
export type ElementType = string | FunctionComponent<any> | ComponentConstructor<any>;
|
|
16
16
|
export type AnuElement<P = Props, T extends ElementType = ElementType> = {
|
|
17
17
|
type: T;
|
|
18
18
|
props: P;
|
|
@@ -2,3 +2,8 @@ import { AnuElement, Ref } from './elements';
|
|
|
2
2
|
export declare const createRef: <T = any>() => Ref<T>;
|
|
3
3
|
export declare const scheduleUpdate: (instance: any, partialState: Record<string, any>, partialStateCallback?: (prevState: any, prevProps: any) => any) => void;
|
|
4
4
|
export declare const render: (elements: AnuElement | AnuElement[], containerDom: Element) => void;
|
|
5
|
+
export declare const unmountComponentAtNode: (containerDom: Element) => void;
|
|
6
|
+
export declare const __testing: {
|
|
7
|
+
flushSync(): void;
|
|
8
|
+
resetGlobals(): void;
|
|
9
|
+
};
|
package/dist/core/reconciler.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.scheduleUpdate = exports.render = exports.createRef = void 0;
|
|
6
|
+
exports.unmountComponentAtNode = exports.scheduleUpdate = exports.render = exports.createRef = exports.__testing = void 0;
|
|
7
7
|
var _domUtils = require("./domUtils");
|
|
8
8
|
const HOST_COMPONENT = 'host';
|
|
9
9
|
const CLASS_COMPONENT = 'class';
|
|
@@ -446,4 +446,43 @@ const render = (elements, containerDom) => {
|
|
|
446
446
|
});
|
|
447
447
|
requestIdleCallback(performWork);
|
|
448
448
|
};
|
|
449
|
-
exports.render = render;
|
|
449
|
+
exports.render = render;
|
|
450
|
+
const unmountComponentAtNode = containerDom => {
|
|
451
|
+
if (!containerDom._rootContainerFiber) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
updateQueue.push({
|
|
455
|
+
from: HOST_ROOT,
|
|
456
|
+
dom: containerDom,
|
|
457
|
+
newProps: {
|
|
458
|
+
children: []
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
requestIdleCallback(performWork);
|
|
462
|
+
};
|
|
463
|
+
exports.unmountComponentAtNode = unmountComponentAtNode;
|
|
464
|
+
const __testing = exports.__testing = {
|
|
465
|
+
flushSync() {
|
|
466
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const syncDeadline = {
|
|
470
|
+
didTimeout: false,
|
|
471
|
+
timeRemaining: () => 999
|
|
472
|
+
};
|
|
473
|
+
while (updateQueue.length > 0 || nextUnitOfWork != null) {
|
|
474
|
+
workLoop(syncDeadline);
|
|
475
|
+
}
|
|
476
|
+
nextUnitOfWork = null;
|
|
477
|
+
pendingCommit = null;
|
|
478
|
+
},
|
|
479
|
+
resetGlobals() {
|
|
480
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
updateQueue.length = 0;
|
|
484
|
+
componentLifecyclesQueue.length = 0;
|
|
485
|
+
nextUnitOfWork = null;
|
|
486
|
+
pendingCommit = null;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _index = _interopRequireWildcard(require("../../index"));
|
|
4
|
+
var _index2 = require("../index");
|
|
5
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
6
|
+
class Counter extends _index.Component {
|
|
7
|
+
state = {
|
|
8
|
+
count: 0
|
|
9
|
+
};
|
|
10
|
+
render() {
|
|
11
|
+
return _index.default.createElement('div', {}, _index.default.createElement('p', {}, `Count: ${this.state.count}`), _index.default.createElement('button', {
|
|
12
|
+
onClick: () => this.setState({
|
|
13
|
+
count: this.state.count + 1
|
|
14
|
+
})
|
|
15
|
+
}, 'Increment'));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const Greeting = ({
|
|
19
|
+
name
|
|
20
|
+
}) => _index.default.createElement('h1', {}, `Hello, ${name}!`);
|
|
21
|
+
describe('render', () => {
|
|
22
|
+
test('renders a function component', () => {
|
|
23
|
+
const {
|
|
24
|
+
getByText
|
|
25
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
26
|
+
name: 'World'
|
|
27
|
+
}));
|
|
28
|
+
expect(getByText('Hello, World!')).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
test('renders a class component', () => {
|
|
31
|
+
const {
|
|
32
|
+
getByText
|
|
33
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
34
|
+
expect(getByText('Count: 0')).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
test('returns a container attached to document.body', () => {
|
|
37
|
+
const {
|
|
38
|
+
container
|
|
39
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
40
|
+
name: 'test'
|
|
41
|
+
}));
|
|
42
|
+
expect(document.body.contains(container)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('queries', () => {
|
|
46
|
+
test('getByRole finds button', () => {
|
|
47
|
+
const {
|
|
48
|
+
getByRole
|
|
49
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
50
|
+
const btn = getByRole('button');
|
|
51
|
+
expect(btn).toBeDefined();
|
|
52
|
+
expect(btn.textContent).toBe('Increment');
|
|
53
|
+
});
|
|
54
|
+
test('getByRole with name option', () => {
|
|
55
|
+
const {
|
|
56
|
+
getByRole
|
|
57
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
58
|
+
const btn = getByRole('button', {
|
|
59
|
+
name: 'Increment'
|
|
60
|
+
});
|
|
61
|
+
expect(btn).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
test('queryByText returns null when not found', () => {
|
|
64
|
+
const {
|
|
65
|
+
queryByText
|
|
66
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
67
|
+
name: 'test'
|
|
68
|
+
}));
|
|
69
|
+
expect(queryByText('Not here')).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
test('getByText throws when not found', () => {
|
|
72
|
+
const {
|
|
73
|
+
getByText
|
|
74
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
75
|
+
name: 'test'
|
|
76
|
+
}));
|
|
77
|
+
expect(() => getByText('Not here')).toThrow();
|
|
78
|
+
});
|
|
79
|
+
test('findByText resolves asynchronously', async () => {
|
|
80
|
+
const {
|
|
81
|
+
findByText
|
|
82
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
83
|
+
name: 'World'
|
|
84
|
+
}));
|
|
85
|
+
const el = await findByText('Hello, World!');
|
|
86
|
+
expect(el).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('fireEvent', () => {
|
|
90
|
+
test('click triggers onClick and re-renders', () => {
|
|
91
|
+
const {
|
|
92
|
+
getByText,
|
|
93
|
+
getByRole
|
|
94
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
95
|
+
expect(getByText('Count: 0')).toBeDefined();
|
|
96
|
+
_index2.fireEvent.click(getByRole('button'));
|
|
97
|
+
expect(getByText('Count: 1')).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
test('multiple clicks accumulate', () => {
|
|
100
|
+
const {
|
|
101
|
+
getByText,
|
|
102
|
+
getByRole
|
|
103
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
104
|
+
_index2.fireEvent.click(getByRole('button'));
|
|
105
|
+
_index2.fireEvent.click(getByRole('button'));
|
|
106
|
+
_index2.fireEvent.click(getByRole('button'));
|
|
107
|
+
expect(getByText('Count: 3')).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('userEvent', () => {
|
|
111
|
+
test('click fires full mouse event sequence', () => {
|
|
112
|
+
const {
|
|
113
|
+
getByText,
|
|
114
|
+
getByRole
|
|
115
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
116
|
+
_index2.userEvent.click(getByRole('button'));
|
|
117
|
+
expect(getByText('Count: 1')).toBeDefined();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('rerender', () => {
|
|
121
|
+
test('updates the component with new props', () => {
|
|
122
|
+
const {
|
|
123
|
+
getByText,
|
|
124
|
+
rerender
|
|
125
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
126
|
+
name: 'Alice'
|
|
127
|
+
}));
|
|
128
|
+
expect(getByText('Hello, Alice!')).toBeDefined();
|
|
129
|
+
rerender(_index.default.createElement(Greeting, {
|
|
130
|
+
name: 'Bob'
|
|
131
|
+
}));
|
|
132
|
+
expect(getByText('Hello, Bob!')).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('waitFor', () => {
|
|
136
|
+
test('polls until assertion passes', async () => {
|
|
137
|
+
const {
|
|
138
|
+
getByText
|
|
139
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
140
|
+
name: 'World'
|
|
141
|
+
}));
|
|
142
|
+
await (0, _index2.waitFor)(() => {
|
|
143
|
+
expect(getByText('Hello, World!')).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
test('rejects when assertion never passes', async () => {
|
|
147
|
+
const {
|
|
148
|
+
queryByText
|
|
149
|
+
} = (0, _index2.render)(_index.default.createElement(Greeting, {
|
|
150
|
+
name: 'World'
|
|
151
|
+
}));
|
|
152
|
+
await expect((0, _index2.waitFor)(() => {
|
|
153
|
+
expect(queryByText('MISSING')).not.toBeNull();
|
|
154
|
+
}, {
|
|
155
|
+
timeout: 100,
|
|
156
|
+
interval: 20
|
|
157
|
+
})).rejects.toThrow();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('act', () => {
|
|
161
|
+
test('flushes state updates synchronously', () => {
|
|
162
|
+
const {
|
|
163
|
+
getByText,
|
|
164
|
+
getByRole
|
|
165
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
166
|
+
(0, _index2.act)(() => {
|
|
167
|
+
_index2.fireEvent.click(getByRole('button'));
|
|
168
|
+
});
|
|
169
|
+
expect(getByText('Count: 1')).toBeDefined();
|
|
170
|
+
});
|
|
171
|
+
test('wraps async callbacks', async () => {
|
|
172
|
+
const {
|
|
173
|
+
getByText
|
|
174
|
+
} = (0, _index2.render)(_index.default.createElement(Counter, {}));
|
|
175
|
+
await (0, _index2.act)(async () => {
|
|
176
|
+
await Promise.resolve();
|
|
177
|
+
});
|
|
178
|
+
expect(getByText('Count: 0')).toBeDefined();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.uninstallSyncScheduler = exports.installSyncScheduler = exports.flushEffects = exports.act = void 0;
|
|
7
|
+
var _reconciler = require("../core/reconciler");
|
|
8
|
+
const FAKE_DEADLINE = {
|
|
9
|
+
didTimeout: false,
|
|
10
|
+
timeRemaining: () => 999
|
|
11
|
+
};
|
|
12
|
+
let _installed = false;
|
|
13
|
+
const installSyncScheduler = () => {
|
|
14
|
+
if (_installed) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
_installed = true;
|
|
18
|
+
window.requestIdleCallback = cb => {
|
|
19
|
+
cb(FAKE_DEADLINE);
|
|
20
|
+
return 0;
|
|
21
|
+
};
|
|
22
|
+
window.cancelIdleCallback = () => {};
|
|
23
|
+
};
|
|
24
|
+
exports.installSyncScheduler = installSyncScheduler;
|
|
25
|
+
const uninstallSyncScheduler = () => {
|
|
26
|
+
_installed = false;
|
|
27
|
+
};
|
|
28
|
+
exports.uninstallSyncScheduler = uninstallSyncScheduler;
|
|
29
|
+
const flushEffects = () => {
|
|
30
|
+
_reconciler.__testing.flushSync();
|
|
31
|
+
};
|
|
32
|
+
exports.flushEffects = flushEffects;
|
|
33
|
+
const act = callback => {
|
|
34
|
+
const result = callback();
|
|
35
|
+
if (result && typeof result.then === 'function') {
|
|
36
|
+
return result.then(() => {
|
|
37
|
+
_reconciler.__testing.flushSync();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
_reconciler.__testing.flushSync();
|
|
41
|
+
};
|
|
42
|
+
exports.act = act;
|