kentcdodds / jest-glamor-react

Jest utilities for Glamor and React
https://npm.im/jest-glamor-react
MIT License
98 stars 24 forks source link

Help setting up snapshots #14

Closed ricardocosta closed 7 years ago

ricardocosta commented 7 years ago

Versions

Configuration

// package.json
"devDependencies": {
    "babel-eslint": "^7.2.3",
    "babel-jest": "^20.0.3",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "enzyme": "^2.9.1",
    "enzyme-to-json": "^1.5.1",
    "glamor": "^3.0.0-3",
    "jest": "^20.0.4",
    "jest-environment-node-debug": "^2.0.0",
    "jest-glamor-react": "^1.4.0",
    "react-addons-test-utils": "^15.6.0",
    "react-test-renderer": "^15.6.1",
  },
"jest": {
    "setupTestFrameworkScriptFile": "<rootDir>/testUtils/customMatchers.js",
    "testEnvironment": "jsdom",
    "snapshotSerializers": [
      "enzyme-to-json/serializer"
    ],
    "transform": {
      "^.+\\.js$": "babel-jest",
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/testUtils/fileTransformer.js"
    }
  }
// <rootDir>/testUtils/customMatchers.js
import { matcher, serializer } from 'jest-glamor-react'
expect.addSnapshotSerializer(serializer)
expect.extend(matcher)

Setup

For this component:

// src/components/ProfilePicture/index.js
import React from 'react'
import { css } from 'glamor'
import picturePath from '../../assets/profile.jpg'

const ProfilePicture = ({ path = picturePath, alt = 'Profile Picture', side = 140 }) => {
  const pictureSide = `${side}px`
  const pictureStyle = css({
    width: pictureSide,
    height: pictureSide,
    borderRadius: '50%',
    borderWidth: side < 100 ? '2px' : '4px',
    borderColor: '#ffffff',
    borderStyle: 'solid'
  })

  return (
    <img {...pictureStyle} src={path} alt={alt} />
  )
}

export default ProfilePicture

With this test:

// src/components/ProfilePicture/__tests__/ProfilePicture.test.js
import React from 'react'
import * as enzyme from 'enzyme'
import toJson from 'enzyme-to-json'
import ProfilePicture from '../../ProfilePicture'

describe('<ProfilePicture />', () => {
  let wrapper

  describe('with default props', () => {
    beforeEach(() => {
      wrapper = shallow(
        <ProfilePicture />
      )
    })

    test('renders the component', () => {
      expect(toJson(wrapper)).toMatchSnapshotWithGlamor(`enzyme.shallow`)
    })
  })
})

The generated snapshot is:

// src/components/ProfilePicture/__tests__/__snapshots__/ProfilePicture.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`enzyme.shallow 1`] = `
<img
  alt="Profile Picture"
  data-css-iqk43n=""
  src="profile.jpg"
/>
`;

Problem The generated snapshot does not contain any CSS class. I was expecting to have css-iqk43n with:

{
    width: 140px;
    height: 140px;
    border-radius: 50%;
    border-width: 4px;
    border-color: #ffffff:
    border-style: solid;
}

Suggested solution I don't really know, since I followed the README. The only difference is that I use glamor's {...pictureStyle} syntax and the README has className, but I tried that and it didn't work.

Thanks in advance.

kentcdodds commented 7 years ago

That's quite odd. It should work with {...pictureStyle}, (we do have tests for it). And it definitely work for me with the className. I don't have time to dig into this issue, so if you could dig a little deeper to figure out what's up that'd be great. Thank you!

ricardocosta commented 7 years ago

I've setup a repository to help investigating this issue: https://github.com/ricardocosta89/jest-glamor-issue-14

After a debug session, I've come to find out that in jest-glamor-react/dist/serializer:

var styles = styleSheet.tags.map(function (tag) {
    return (/* istanbul ignore next */tag.textContent || ''
  );
}).join('\n');

Therefore, no styles are available at this point inprint:

if (styles) {
  return `${styles}\n\n${printedVal}`;
} else {
  return printedVal;
}

Investigating the styles generation... styleSheet:

{
  "isSpeedy": false,
  "tags": [{}],
  "maxLength": 65000,
  "ctr": 0,
  "injected": true,
  "plugins": {
    "fns": [null,null],
    "media": {"fns": []},
    "fontFace": {"fns": []},
    "keyframes": {
      "fns": [null]
    }
  },
  "inserted": {},
  "registered": {}
}

We can see that styleSheet.registered is empty.

Downgrading to glamor@2.20.25 produces a correct stylesheet:

{
  "isSpeedy": false,
  "tags": [{}],
  "maxLength": 65000,
  "ctr": 1,
  "injected": true,
  "plugins": {
    "fns": [null,null],
    "media": {"fns": []},
    "fontFace": {
      "fns": []}
    ,"keyframes": {
      "fns": [null]
    }
  },
  "inserted": {"iqk43n": true},
  "registered": {
    "iqk43n": {
      "id": "iqk43n",
      "style": {
        "label": [],
        "width": "140px",
        "height": "140px",
        "borderRadius": "50%",
        "borderWidth": "4px",
        "borderColor": "#ffffff",
        "borderStyle": "solid"
      },
      "label": "",
      "type": "css"
    }
  }
}

Conclusion: The issue is a breaking change from glamor's v2 to v3. Will continue to investigate to see if I can find out exactly what is causing this.

kentcdodds commented 7 years ago

Thanks for the update and the continuous investigation!

ricardocosta commented 7 years ago

I can't seem to reach any conclusion. The difference is that, by the time print is executed, styleSheet already has the registered object with the correct information in v2 but not in v3.

We might need to ask in Glamor repo about this change from v2 to v3, since I'm not figuring out where the "magic" of populating styleSheet is happening. ๐Ÿค”

kentcdodds commented 7 years ago

cc @threepointone, who should we talk with about problems like this? Could anyone help us know what's changed in Glamor 3?

threepointone commented 7 years ago

Cc @donaldpipowitch

donaldpipowitch commented 7 years ago

I talked with @otbe about this. He really knows the internals :) I just opened issues and hit the publish button.

It looks like the registered cache was previously just an object (?) which was added as a property directly to styleSheet (see here). In v3 the registered cache has a Map-like API (see here and here). It is not exported as a property to styleSheet, but together with inserted as caches (see here).

We can see that styleSheet.registered is empty.

So maybe we just need to look into caches.registered?

Maybe @otbe knows more. He's the best โค๏ธ

ricardocosta commented 7 years ago

@donaldpipowitch I took a look at what is available with require('glamor') using VSCode debug and unfortunately no caches is available. Which is very weird, as all of the other exports are available (e.g. simulate, simulations, isLikeRule, cssLabels, cssFor, ...). ๐Ÿค”

otbe commented 7 years ago

@donaldpipowitch I think your conclusion is correct. Both caches are no longe attached to the exported StyleSheet instance. As of now they are exported via caches.

@ricardocosta89 thats strange. I did a quick test and it works as expected:

bildschirmfoto 2017-07-16 um 20 29 57

Always open for better solutions :)

ricardocosta commented 7 years ago

@otbe I think it's something related with using the debug console in VSCode or the dist version of jest-glamor-react.

If I add a dummy const s = require('glamor') in the test file and set a breakpoint, then I have:

JSON.stringify(s.caches)
"{"inserted":{"cache":{}},"registered":{"cache":{}}}"

If the breakpoint is set on jest-glamor-react/dist/serializer.js (line 10), then _require does not have caches.

ricardocosta commented 7 years ago

@kentcdodds It seems there's a complete API change here. I tried to see if I could extract the same kind of string given the Map-like API, but I couldn't get very far. ๐Ÿ˜ž Do you confirm that the desired string for each style (the result of tag.textContent) is <nodeSelector>{<style>} (e.g. .something{padding:20px;borderWidth:2px})? Or is there something more?

Also, @otbe, just a question: Will this label always be present?

kentcdodds commented 7 years ago

The value of tag.textContent should look something like this:

.css-5qt57y,[data-css-5qt57y]{color:red;font-size:20px;}.css-1evjunj,[data-css-1evjunj]{color:blue;font-size:20px;}.css-s7n3hr,[data-css-s7n3hr]{color:red;font-size:25px;}.css-1xzrot3,[data-css-1xzrot3]{color:green;}.css-13vote6,[data-css-13vote6]{color:green;font-size:20px;}
ricardocosta commented 7 years ago

@kentcdodds I have the matcher and index tests passing with the following to get the styles using glamor v3 (Object.values() would be helpful here ๐Ÿ˜ž ):

const styleCache = caches.registered.cache

const styles = Object.keys(styleCache)
  .map(style => cssFor(glamorCss(styleCache[style].style)))
  .join('\n')

const ast = css.parse(styles)

However, the custom-sheet test does not use glamor, and therefore it still fails... any advice on how to tackle this situation?

On another point, and I'd like @otbe's help here, for jest-glamor-react tests, the caches are available and populated correctly in serializer.js. However, when I copy the dist folder to node_modules/jest-glamor-react in my sample project, caches are no longer populated when serializer.js is executed. Any idea why? ๐Ÿค”

threepointone commented 7 years ago

Quick note - glamor's default version is now v2 again, the v3 version leaked out to being default on npm by mistake

ricardocosta commented 7 years ago

Hmm... the gatsby plugin I'm using has "glamor": "next",. I'll check what glamor version is resolved now.

threepointone commented 7 years ago

Dammit next still points to v3. Will fix that too in a bit.

ricardocosta commented 7 years ago

Thanks @threepointone ๐Ÿ™

kentcdodds commented 7 years ago

Wahoo!

ricardocosta commented 7 years ago

Well, at least now we know a bit of the path when v3 is ready. ๐Ÿ˜„ And I have learned some things.