Skip to content
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

breaking: Migrate codebase to typescript #101

Merged
merged 28 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5a7af8a
refactor: Migrate codebase to typescript
Ne3l Aug 15, 2022
7059af2
fix test
Ne3l Aug 15, 2022
b7545bb
use node14
Ne3l Aug 15, 2022
225d0e2
fix rebase
Ne3l Sep 11, 2022
e25a27c
Add feedback
Ne3l Sep 11, 2022
b6e868a
fix test
Ne3l Sep 11, 2022
17c5539
use StyleSheet
Ne3l Sep 11, 2022
101a212
fix rebase to-have-styles
Ne3l Sep 18, 2022
5252b75
fix tests
Ne3l Sep 18, 2022
59c41a8
Simplify code
Ne3l Sep 18, 2022
87184c0
Apply feedback
Ne3l Sep 18, 2022
a105a87
fix rebase d38fc801b0bd01934166cd50634cdb9530a64a4a
Ne3l Sep 26, 2022
470b57d
use RNTL tsconfig
Ne3l Sep 26, 2022
34c2d0b
change ts-ignore with ts-expect-error.
Ne3l Sep 26, 2022
487079f
Fix test
Ne3l Sep 26, 2022
9928fb2
refactor: tweak export matcher types
mdjastrzebski Sep 28, 2022
785a4e7
refactor: cleanup toHaveStyle
mdjastrzebski Sep 28, 2022
9a4c58a
chore: vlaidate types on CI
mdjastrzebski Sep 28, 2022
7dd9d6d
refactor: disable expected type errors
mdjastrzebski Sep 28, 2022
c738e45
refactor: re-apply types to toHaveStyle from main branch
mdjastrzebski Sep 28, 2022
cf7ea72
refactor: apply type assertions
mdjastrzebski Sep 28, 2022
0aa0f9d
refactor: reorder imports
mdjastrzebski Sep 28, 2022
071b059
refactor: re-order
mdjastrzebski Sep 28, 2022
bce1a22
refactor: restore `getType()`
mdjastrzebski Sep 28, 2022
fef4cfc
refactor: review logic changes
mdjastrzebski Sep 28, 2022
821615b
refactor: restore output to dist folder
mdjastrzebski Sep 28, 2022
5cf066e
fix: not.toBeEnabled == toBeDisabled.
mdjastrzebski Sep 29, 2022
bb67f0f
refactor: fix undefined StyleSheet.flatten() result handling
mdjastrzebski Sep 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions src/__tests__/__snapshots__/to-have-style.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@ exports[`.toHaveStyle handles negative test cases 1`] = `
- Expected

backgroundColor: blue;
- transform: [{"scale": 1}];
+ transform: [{"scale": 2}];"
transform: [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i had the doubt when doing the change, do we want to output everything or just the result which is different than the expectation?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind this is that particular style attributes are not divisible, they need to match as units.

Normally this is not an issue as most style attributes are string, number, etc. The only two Array attributes are transform and fontVariant and we want to output the whole array in this case.

{
- "scale": 1
+ "scale": 2
+ },
+ {
+ "rotate": "45deg"
}
];"
`;

exports[`.toHaveStyle handles transform when transform undefined 1`] = `
"expect(element).toHaveStyle()

- Expected

- transform: [{"scale": 1}];
- transform: [
- {
- "scale": 1
- }
- ];
+ transform: undefined;"
`;
64 changes: 27 additions & 37 deletions src/to-have-style.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,48 @@
import { StyleSheet } from 'react-native';
import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native';
import type { ReactTestInstance } from 'react-test-renderer';
import { matcherHint } from 'jest-matcher-utils';
import { diff } from 'jest-diff';
import chalk from 'chalk';
import type { ReactTestInstance } from 'react-test-renderer';
import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native';
import { StyleSheet } from 'react-native';
import { checkReactElement, display } from './utils';
import { checkReactElement } from './utils';

type Style = TextStyle | ViewStyle | ImageStyle;
type StyleLike = Record<string, unknown>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the styleLike? passing an undefined as a styleProp is valid but the flatten won't return a Record

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing out potential undefined return from StyleSheet.flatten() I've added check for that.

Regarding why Record and not concrete Style, it has to do with Object.keys() not handling well Style, so we either need to we sprinkle Object.keys or StyleSheet.flatten with as typecasts. Since flattened style is essential Record<string, any> or undefined, then it makes sense to use Record as it does not quire additional typecasts in the checking functions downstream.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for heads up, I think we good for now and we'll have to adjust JS implementation when something new comes to replace flatten().


const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;

function printoutStyles(style: Style) {
return getKeys(style)
function printoutStyles(style: StyleLike) {
return Object.keys(style)
.sort()
.map((prop) => `${prop}: ${display(style[prop])};`)
.map((prop) =>
Array.isArray(style[prop])
? `${prop}: ${JSON.stringify(style[prop], null, 2)};`
: `${prop}: ${style[prop]};`,
)
.join('\n');
}

/**
* Recursively narrows down the properties in received to those with counterparts in expected
* Narrows down the properties in received to those with counterparts in expected
*/
function narrow(expected: Style, received: Style) {
return getKeys(received)
function narrow(expected: StyleLike, received: StyleLike) {
return Object.keys(received)
.filter((prop) => expected[prop])
.reduce((obj, prop) => {
if (Array.isArray(expected[prop]) && Array.isArray(received[prop])) {
return Object.assign(obj, {
// @ts-ignore not sure how to type it
[prop]: expected[prop]?.map((_, i) =>
// @ts-ignore not sure how to type it
narrow(expected[prop][i], received[prop][i]),
),
});
}

return Object.assign(obj, {
[prop]: received[prop],
});
}, {});
.reduce(
(obj, prop) =>
Object.assign(obj, {
[prop]: received[prop],
}),
{},
);
}

// Highlights only style rules that were expected but were not found in the
// received computed styles
function expectedDiff(expected: Style, received: Style) {
function expectedDiff(expected: StyleLike, received: StyleLike) {
const receivedNarrow = narrow(expected, received);

const diffOutput = diff(printoutStyles(expected), printoutStyles(receivedNarrow));

// TODO: What's this case?
if (diffOutput == null) return '';
// Remove the "+ Received" annotation because this is a one-way diff
return diffOutput.replace(`${chalk.red('+ Received')}\n`, '');
return diffOutput?.replace(`${chalk.red('+ Received')}\n`, '') ?? '';
}

export function toHaveStyle(
Expand All @@ -60,13 +52,11 @@ export function toHaveStyle(
) {
checkReactElement(element, toHaveStyle, this);

const expected = StyleSheet.flatten(style);
const received = StyleSheet.flatten(element.props.style);
const expected = StyleSheet.flatten(style) as StyleLike;
const received = StyleSheet.flatten(element.props.style) as StyleLike;

return {
pass: Object.entries(expected).every(([prop, value]) =>
this.equals(received?.[prop as keyof typeof received], value),
),
pass: Object.entries(expected).every(([prop, value]) => this.equals(received?.[prop], value)),
message: () => {
const matcher = `${this.isNot ? '.not' : ''}.toHaveStyle`;
return [matcherHint(matcher, 'element', ''), expectedDiff(expected, received)].join('\n\n');
Expand Down