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

Refactor Link to add more cheap unit tests #4553

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

OleksandrNechai
Copy link
Collaborator

@OleksandrNechai OleksandrNechai commented Sep 12, 2024

[FX-NNNN]

Description

This if a follow up of f8e5c8f and #4547. Where I did not test code due to inability to add cheap test.

Extract all view logic into separate pure function. This allows adding many tests that run in few ms, raising confidence in our code without making tests suits running forever.

How to test

Locally yarn test:unit:watch

Development checks

  • Add changeset according to guidelines (if needed)
  • Double check if picasso-tailwind-merge requires major update (check its README.md)
  • Read CONTRIBUTING.md and Component API principles
  • Make sure that additions and changes on the design follow Toptal's BASE design, and it's been already discussed with designers at #-base-core
  • Annotate all props in component with documentation
  • Create examples for component
  • Ensure that deployed demo has expected results and good examples
  • Ensure the changed/created components have not caused accessibility issues. How to use accessibility plugin in storybook.
  • Self reviewed
  • Covered with tests (visual tests included)

Breaking change

  • codemod is created and showcased in the changeset
  • test alpha package of Picasso in StaffPortal

All development checks should be done and set checked to pass the
GitHub Bot: TODOLess action

PR commands

List of available commands:

  • @toptal-bot run package:alpha-release - Release alpha version
  • @toptal-anvil ping reviewers - Ping FX team for review
PR Review Guidelines

When to approve? ✅

You are OK with merging this PR and

  1. You have no extra requests.
  2. You have optional requests.
    1. Add nit: to your comment. (ex. nit: I'd rename this variable from makeCircle to getCircle)

When to request changes? ❌

You are not OK with merging this PR because

  1. Something is broken after the changes.
  2. Acceptance criteria is not reached.
  3. Code is dirty.

When to comment (neither ✅ nor ❌)

You want your comments to be addressed before merging this PR in cases like:

  1. There are leftovers like unnecessary logs, comments, etc.
  2. You have an opinionated comment regarding the code that requires a discussion.
  3. You have questions.

How to handle the comments?

  1. An owner of a comment is the only one who can resolve it.
  2. An owner of a comment must resolve it when it's addressed.
  3. A PR owner must reply with ✅ when a comment is addressed.

@OleksandrNechai OleksandrNechai requested a review from a team as a code owner September 12, 2024 15:18
Copy link

changeset-bot bot commented Sep 12, 2024

⚠️ No Changeset found

Latest commit: b32a83c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@toptal-devbot
Copy link
Collaborator

toptal-devbot commented Sep 12, 2024

Fails
🚫

The pull request title is missing a Jira issue code. Correct format '[ASD-123] Add a cool feature'. If you're working without a Jira issue then add a 'no-jira' label to your pull request.

Generated by 🚫 dangerJS against b32a83c

@OleksandrNechai OleksandrNechai self-assigned this Sep 12, 2024
nativeHTMLAttributes: Omit<Props, keyof BaseProps>
}

/* eslint-disable complexity */
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ES lint wants code to have no more 10 branches. For this kind of function this does not make sense, code is not complex (this error existed before refactoring too)

}

/* eslint-disable complexity */
export const calculateViewModel = (props: Props): ViewModel => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This pure function return all info needed to render Link without rendering Link.

Copy link
Contributor

Choose a reason for hiding this comment

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

@OleksandrNechai Do you think that calculateViewModel might do too many things at once? I tried to split the returned pieces of "model" into groups, but have not succeeded, though.

In terms of code structure the function seems to be a good approach, but I am a bit worried that it does so many unrelated calculations, as in case of Link component the model itself has so many heterogeneous properties, so it might affect the readability a bit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it totally does a lot of things. And yet I find this function simple because when you look at it, each section of code calculates one prop and they are mostly not intertwined. I tried to decompose this function in smaller ones, and result was not great. Extra functions would take 5 params and return one output and hide 2 lines of code. This make code more complicated, as I did not manage to extract "deep" function: https://medium.com/@nathan.fooo/4-notes-modules-should-be-deep-ba5671c4288c

Copy link
Contributor

Choose a reason for hiding this comment

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

@OleksandrNechai Agree!
Although the function still looks shallow as on the picture from https://medium.com/@nathan.fooo/4-notes-modules-should-be-deep-ba5671c4288c, I have not strong preference 👍

Screenshot 2024-09-13 at 18 42 19

@@ -99,3 +100,222 @@ describe('Link', () => {
expect(getByTestId('foo')).not.toHaveAttribute('href')
})
})

describe('calculateViewModel', () => {
it('should apply default values when no props are provided', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: from https://toptal-core.atlassian.net/wiki/spaces/ENG/pages/1120830304/Frontend+Testing+-+Unit+testing#It

Do not start test description with should, because it doesn’t bring value.

expect(result.ariaDisabled).toBe(true)
})

it('should apply visited class when visited is true', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: from https://toptal-core.atlassian.net/wiki/spaces/ENG/pages/1120830304/Frontend+Testing+-+Unit+testing#Describe

We have to wrap the use case with describe section to accentuate a condition when that test case must happen.

@sashuk sashuk requested a review from a team September 12, 2024 15:59
})

describe('when href, target, rel and onClick are provided', () => {
it('applies correct href, target, rel, and onClick', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

@OleksandrNechai I wonder what does it mean "correct" href, target, etc.? Describing it might take more time than writing tests, but then maybe there is no value in this test?

@ruslan-sed ruslan-sed self-requested a review September 16, 2024 10:10
Copy link
Contributor

@ruslan-sed ruslan-sed left a comment

Choose a reason for hiding this comment

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

Great job!
Very nice way to simplify component testing by extracting logic to helper function 👍

const result = calculateViewModel(props)

expect(result.href).toBe('https://example.com')
expect(result.target).toBe('_blank')
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe let's use a variable for those values?

Copy link
Contributor

@sashuk sashuk left a comment

Choose a reason for hiding this comment

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

Looks good!

@OleksandrNechai OleksandrNechai force-pushed the fx-refactor-link-away-from-being-too-complex branch from 4d500ab to a224ead Compare September 17, 2024 20:48
@OleksandrNechai OleksandrNechai force-pushed the fx-refactor-link-away-from-being-too-complex branch from a224ead to b32a83c Compare September 18, 2024 08:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants