-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add warning and failing test decorators
#5929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a1979c6
c8aabc1
3ec60c8
7097586
e636b32
d710c99
db08c20
0c4eb29
15544ca
40fb0b6
097514a
f9d9de9
6bf02cd
f8e2c43
a75a3ce
9af8768
cd1c8c9
9bb5e21
17ace4e
3ce0604
993a530
cf02348
b3e1e86
7f8f060
b0b528d
5ed5756
08dab0d
934cff0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,9 +1,31 @@ | ||||||
| import { View, Button, StyleSheet } from 'react-native'; | ||||||
| import { View, Button, StyleSheet, Text } from 'react-native'; | ||||||
| import React, { ReactNode, useEffect, useState } from 'react'; | ||||||
| import { runTests, configure } from './RuntimeTestsApi'; | ||||||
| import { LockObject } from './types'; | ||||||
|
|
||||||
| let renderLock: LockObject = { lock: false }; | ||||||
| export class ErrorBoundary extends React.Component< | ||||||
| { children: React.JSX.Element | Array<React.JSX.Element> }, | ||||||
| { hasError: boolean } | ||||||
| > { | ||||||
| constructor(props: { children: React.JSX.Element | Array<React.JSX.Element> }) { | ||||||
| super(props); | ||||||
| this.state = { hasError: false }; | ||||||
| } | ||||||
|
|
||||||
| static getDerivedStateFromError(_: unknown) { | ||||||
| // Update state so the next render will show the fallback UI. | ||||||
| return { hasError: true }; | ||||||
| } | ||||||
|
|
||||||
| render() { | ||||||
| if (this.state.hasError) { | ||||||
| return <Text>Something went wrong.</Text>; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| return this.props.children; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| export default function RuntimeTestsRunner() { | ||||||
| const [component, setComponent] = useState<ReactNode | null>(null); | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,23 @@ | ||
| import { Component, MutableRefObject, ReactElement, useRef } from 'react'; | ||
| import type { | ||
| NullableTestValue, | ||
| LockObject, | ||
| Operation, | ||
| SharedValueSnapshot, | ||
| TestCase, | ||
| TestConfiguration, | ||
| TestSuite, | ||
| TestSummary, | ||
| TestValue, | ||
| TrackerCallCount, | ||
| import { | ||
| type NullableTestValue, | ||
| type LockObject, | ||
| type Operation, | ||
| type SharedValueSnapshot, | ||
| type TestCase, | ||
| type TestConfiguration, | ||
| type TestSuite, | ||
| type TestSummary, | ||
| type TestValue, | ||
| type TrackerCallCount, | ||
| ComparisonMode, | ||
| DescribeDecorator, | ||
| TestDecorator, | ||
| } from './types'; | ||
Latropos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import { TestComponent } from './TestComponent'; | ||
| import { render, stopRecordingAnimationUpdates, unmockAnimationTimer } from './RuntimeTestsApi'; | ||
| import { getTrackerCallCount, render, stopRecordingAnimationUpdates, unmockAnimationTimer } from './RuntimeTestsApi'; | ||
| import { makeMutable, runOnUI, runOnJS, SharedValue } from 'react-native-reanimated'; | ||
| import { color, formatString, indentNestingLevel } from './stringFormatUtils'; | ||
| import { applyMarkdown, color, formatString, indentNestingLevel } from './stringFormatUtils'; | ||
| import { createUpdatesContainer } from './UpdatesContainer'; | ||
| import { Matchers, nullableMatch } from './Matchers'; | ||
| import { assertMockedAnimationTimestamp, assertTestCase, assertTestSuite } from './Asserts'; | ||
|
|
@@ -86,16 +89,20 @@ export class TestRunner { | |
| } | ||
| this._wasRenderedNull = !component; | ||
| this._renderLock.lock = true; | ||
| this._renderHook(component); | ||
| try { | ||
| this._renderHook(component); | ||
| } catch (e) { | ||
| console.log(e); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we ignore all exceptions? 🤔 |
||
| } | ||
| return this.waitForPropertyValueChange(this._renderLock, 'lock'); | ||
| } | ||
|
|
||
| public async clearRenderOutput() { | ||
| return await this.render(null); | ||
| } | ||
|
|
||
| public describe(name: string, buildSuite: () => void, only = false, skip = false) { | ||
| if (only) { | ||
| public describe(name: string, buildSuite: () => void, decorator: DescribeDecorator | null) { | ||
| if (decorator === DescribeDecorator.ONLY) { | ||
| this._includesOnly = true; | ||
| } | ||
|
|
||
|
|
@@ -116,38 +123,63 @@ export class TestRunner { | |
| } | ||
|
|
||
| this._testSuites.splice(index, 0, { | ||
| name, | ||
| name: applyMarkdown(name), | ||
| buildSuite, | ||
| testCases: [], | ||
| nestingLevel: (this._currentTestSuite?.nestingLevel || 0) + 1, | ||
| only: !!(only || this._currentTestSuite?.only), | ||
| skip: !!(skip || this._currentTestSuite?.skip), | ||
| decorator: decorator ? decorator : this._currentTestSuite?.decorator ? this._currentTestSuite?.decorator : null, | ||
Latropos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
| } | ||
|
|
||
| public test(name: string, run: () => void, only = false, skip = false) { | ||
| public test(name: string, run: () => void, decorator: TestDecorator | null, warningMessage = '') { | ||
| assertTestSuite(this._currentTestSuite); | ||
| if (only) { | ||
| if (decorator === TestDecorator.ONLY) { | ||
| this._includesOnly = true; | ||
| } | ||
| this._currentTestSuite.testCases.push({ | ||
| name, | ||
| run, | ||
| componentsRefs: {}, | ||
| callsRegistry: {}, | ||
| errors: [], | ||
| only: only, | ||
| skip: skip, | ||
| }); | ||
| this._currentTestSuite.testCases.push( | ||
| decorator === TestDecorator.WARN || decorator === TestDecorator.FAILING | ||
| ? { | ||
| name: applyMarkdown(name), | ||
| run, | ||
| componentsRefs: {}, | ||
| callsRegistry: {}, | ||
| errors: [], | ||
| decorator, | ||
| warningMessage: warningMessage, | ||
| } | ||
| : { | ||
| name: applyMarkdown(name), | ||
| run, | ||
| componentsRefs: {}, | ||
| callsRegistry: {}, | ||
| errors: [], | ||
| decorator, | ||
| }, | ||
| ); | ||
Latropos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public testEach<T>(examples: Array<T>, only = false, skip = false) { | ||
| public testEachErrorMsg<T>(examples: Array<T>, decorator: TestDecorator) { | ||
| return (name: string, expectedWarning: string, testCase: (example: T) => void) => { | ||
| examples.forEach((example, index) => { | ||
| const currentTestCase = async () => { | ||
| await testCase(example); | ||
| }; | ||
| this.test( | ||
| formatString(name, example, index), | ||
| currentTestCase, | ||
| decorator, | ||
| formatString(expectedWarning, example, index), | ||
| ); | ||
| }); | ||
| }; | ||
| } | ||
| public testEach<T>(examples: Array<T>, decorator: TestDecorator | null) { | ||
| return (name: string, testCase: (example: T, index?: number) => void) => { | ||
| examples.forEach((example, index) => { | ||
| const currentTestCase = async () => { | ||
| await testCase(example, index); | ||
| }; | ||
| this.test(formatString(name, example, index), currentTestCase, only, skip); | ||
| this.test(formatString(name, example, index), currentTestCase, decorator); | ||
| }); | ||
| }; | ||
| } | ||
|
|
@@ -221,16 +253,14 @@ export class TestRunner { | |
| let skipTestSuite = testSuite.skip; | ||
|
|
||
| if (this._includesOnly) { | ||
| skipTestSuite = skipTestSuite || !testSuite.only; | ||
| skipTestSuite = skipTestSuite || !(testSuite.decorator === DescribeDecorator.ONLY); | ||
|
|
||
| for (const testCase of testSuite.testCases) { | ||
| if (testCase.only) { | ||
| if (testCase.decorator === TestDecorator.ONLY) { | ||
| skipTestSuite = false; | ||
| } else testCase.skip = testCase.skip || !testSuite.only; | ||
| delete testCase.only; | ||
| } else testCase.skip = testCase.skip || !(testSuite.decorator === DescribeDecorator.ONLY); | ||
| } | ||
| } | ||
| delete testSuite.only; | ||
| testSuite.skip = skipTestSuite; | ||
| } | ||
|
|
||
|
|
@@ -280,7 +310,39 @@ export class TestRunner { | |
| await testSuite.beforeEach(); | ||
| } | ||
|
|
||
| await testCase.run(); | ||
| if (testCase.decorator === TestDecorator.FAILING || testCase.decorator === TestDecorator.WARN) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch should be moved to separate function |
||
| const consoleTrackerRef = testCase.decorator === TestDecorator.FAILING ? 'console.error' : 'console.warn'; | ||
| const message = makeMutable(''); | ||
|
|
||
| const newConsoleFuncJS = (warning: string) => { | ||
| this.callTracker(consoleTrackerRef); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it could be just a simple sharedValue (makeMutable) instead of using callTrucker |
||
| message.value = warning.split('\n\nThis error is located at:')[0]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then the test doesn't pass. Do you think we need a possibility to handle multiple |
||
| }; | ||
| console.error = newConsoleFuncJS; | ||
| console.warn = newConsoleFuncJS; | ||
|
Comment on lines
+321
to
+322
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you ever restore original implementation of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, since you have to reload the app to re-run the tests and it resets it. |
||
|
|
||
| const callTrackerCopy = this.callTracker; | ||
|
|
||
| runOnUI(() => { | ||
| 'worklet'; | ||
| const newConsoleFuncUI = (warning: string) => { | ||
| callTrackerCopy(consoleTrackerRef); | ||
| message.value = warning.split('\n\nThis error is located at:')[0]; | ||
| }; | ||
| console.error = newConsoleFuncUI; | ||
| console.warn = newConsoleFuncUI; | ||
| })(); | ||
|
Comment on lines
+326
to
+334
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps you mean runOnUI blocking? |
||
|
|
||
| await testCase.run(); | ||
|
|
||
| this.expect(getTrackerCallCount(consoleTrackerRef)).toBeCalled(1); | ||
| if (testCase.warningMessage) { | ||
| this.expect(message.value).toBe(testCase.warningMessage, ComparisonMode.STRING); | ||
| } | ||
|
Comment on lines
+338
to
+341
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| } else { | ||
| await testCase.run(); | ||
| } | ||
|
|
||
| this.showTestCaseSummary(testCase, testSuite.nestingLevel); | ||
|
|
||
| if (testSuite.afterEach) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.