I've noticed that if you run any sort of fireEvent in a react-testing-library test, it isn't cleaned up after the test by afterEach(cleanup).
For example, the following test will pass for both tests, even though they are both expecting completely different things but are identical in every way.
afterEach(cleanup);
describe("<ToggleIconButton>", () => {
// TEST 1
it("shows the <VisibileIcon> svg icon on click", () => {
const { container } = render(<ToggleIconButton />);
const button = container.querySelector("button");
fireEvent.click(button);
const svg = container.querySelector("svg");
expect(svg.getAttribute("data-testid")).toBe("visibleIcon");
});
// TEST 2
it("shows the <NotVisibleIcon> svg icon on double-click", () => {
const { container } = render(<ToggleIconButton />);
const button = container.querySelector("button");
fireEvent.click(button);
const svg = container.querySelector("svg");
expect(svg.getAttribute("data-testid")).toBe("notVisibileIcon");
});
});
The reason for this is because the fireEvent.click(button) in TEST 1 carries over into TEST 2, so when the fireEvent.click(button) is called in TEST 2, it acts like it was a double-click.
How do I ensure that the fireEvent's are cleaned up after each test?
Related
When I try to use https://usehooks-ts.com/react-hook/use-local-storage in Next.js in the following way, I get
Unhandled Runtime Error Error: Text content does not match
server-rendered HTML.
See more info here:
https://nextjs.org/docs/messages/react-hydration-error
const [toleranceH, setToleranceH] = useLocalStorage<number>('toleranceH', 3);
const [toleranceS, setToleranceS] = useLocalStorage<number>('toleranceS', 3);
const [toleranceL, setToleranceL] = useLocalStorage<number>('toleranceL', 3);
const [results, setResults] = useState<MegaColor[]>([]);
const debouncedToleranceH = useDebounce<number>(toleranceH, 200);
const debouncedToleranceS = useDebounce<number>(toleranceS, 200);
const debouncedToleranceL = useDebounce<number>(toleranceL, 200);
useEffect(() => {
const targetColorDetailsObject = getColorDetailsObject(targetColor);
const degreeTolerance = (360 / 100) * debouncedToleranceH;
const [hueMin, hueMax] = getHueTolerance(targetColorDetailsObject.hue(), degreeTolerance);
const filteredColors = getFilteredColors(targetColorDetailsObject, loadedMegaColors, hueMin, hueMax, debouncedToleranceS, debouncedToleranceL);
setResults(filteredColors);
return () => {
// console.log('cleanup');
};
}, [targetColor, loadedMegaColors, debouncedToleranceH, debouncedToleranceS, debouncedToleranceL]);
From that help page, I still can't figure out what to adjust so that I can use both useLocalStorage and useDebounce.
I found https://stackoverflow.com/a/73411103/470749 but don't want to forcefully set a localStorage value (it should only be set by the user).
I'd suggest checking out this excellent post on rehydration by Josh W Comeau.
Since Next.js pre-renders every page by default you need to ensure that the component in which you are calling window.localstorage is only rendered on the client.
A simple solution is to:
Keep a hasMounted state
const [hasMounted, setHasMounted] = useState(false);
Toggle it inside a useEffect
useEffect(() => {
// This will only be called once the component is mounted inside the browser
setHasMounted(true);
}, []);
Add a check so that Next.js won't complain about prerendering stuff on the server that won't match the stuff that gets rendered on the client
if (!hasMounted) {
return null;
}
Ensure that the client-side stuff comes after the check
To make it more reusable you could use one of these two methods which essentially do the same:
ClientOnly Component
function ClientOnly({ children, ...delegated }) {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
/**
* Could also replace the <div></div> with
* <></> and remove ...delegated if no need
*/
return (
<div {...delegated}>
{children}
</div>
);
}
...
<ClientOnly>
<MyComponent /> // <--- client only stuff, safe to use useLocalStorage in here
</ClientOnly>
or
Custom useHasMounted hook
function useHasMounted() {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
...
function ParentComponent() {
const hasMounted = useHasMounted();
if (!hasMounted) {
return null;
}
return (
<MyComponent />
);
}
...
function MyComponent() {
const [toleranceH, setToleranceH] = useLocalStorage<number>('toleranceH', 3);
const [toleranceS, setToleranceS] = useLocalStorage<number>('toleranceS', 3);
const [toleranceL, setToleranceL] = useLocalStorage<number>('toleranceL', 3);
...
}
...
Note:
By overdoing this or using this method at the top level of your component tree, you are killing the Next.js prerendering capabilities and turning your app into more of a "client-side heavy" app (see performance implications). If you are using window.localstorage (outside of components, where you don't have useEffect available), you should always wrap with:
if (typeof window !== 'undefined') {
// client-side code
}
So, I was doing some testing with Rewire with Mocha, and I noticed behavior that seems odd to me. using myModule.__set__() seems to actually set the specified variable in the the specified module (myModule), AND in the global scope of the current module (the one that ran the __set__()). For example:
This code runs after running mocha test:
Test.js:
var rewire = require("rewire")
var sinon = require("sinon")
var test2 = rewire("./test2.js")
var expect = require("chai").expect
var spy = sinon.spy()
describe("test", function () {
beforeEach(function () {
test2.__set__("console", { log: spy })
})
it("test should be equal to 3", function () {
test2.test1()
console.log("testing5")
expect(spy.callCount).to.equal(3)
})
})
Test2.js:
module.exports = {
test1() {
console.log("test")
console.log("test")
console.log("test")
}
}
Here, I would expect "testing5" to actually be logged to the console, but the 3 "test"s to just be recorded in the spy, and the callCount to be equal to 3. This is not what is happening however. The "testing5" is not being logged, but is being recorded to that sinon spy, and the test is failing, because the callCount is 4, and not 3. To me this doesn't seem like this would be intended. Is there something I'm doing wrong?
In my new React Native app, I want to add some Jest tests.
One component renders a background image, which is located directly in the project in assets folder.
Now I stumbled about how to test if this image is actually taken from this path, therefore present in the component, and rendered correctly.
I tried using toHaveStyle from #testing-library/jest-native with a container, which returned the error toHaveStyleis not a function. Then I tried the same with queryByTestId, same error. When I do expect(getByTestId('background').toBeInTheDocument); then I feel this is useless, because it only checks if an element with this testId is present, but not the image source.
Please, how can I test this? Does it actually make sense to test an image source after all?
Here is my code:
1.) The component that should be tested (Background):
const Background: React.FC<Props> = () => {
const image = require('../../../../assets/images/image.jpg');
return (
<View>
<ImageBackground testID="background" source={image} style={styles.image}></ImageBackground>
</View>
);
};
2.) The test:
import React from 'react';
import {render, container} from 'react-native-testing-library';
import {toHaveStyle} from '#testing-library/jest-native';
import '#testing-library/jest-native/extend-expect';
import Background from '../Background';
describe('Background', () => {
test('renders Background image', () => {
const {getByTestId} = render(<Background></Background>);
expect(getByTestId('background').toBeInTheDocument);
/* const container = render(<Background background={background}></Background>);
expect(container).toHaveStyle(
`background-image: url('../../../../assets/images/image.jpg')`,
); */
/* expect(getByTestId('background')).toHaveStyle(
`background-image: url('../../../../assets/images/image.jpg')`,
); */
});
});
If you're using #testing-library/react rather than #testing-library/react-native, and you have an alt attribute on your image, you can avoid using getByDataTestId and instead use getByAltText.
it('uses correct src', async () => {
const { getByAltText } = await render(<MyComponent />);
const image = getByAltText('the_alt_text');
expect(image.src).toContain('the_url');
// or
expect(image).toHaveAttribute('src', 'the_url')
});
Documentation.
Unfortunately, it appears that React Native Testing Library does not include getByAltText. (Thank you, #P.Lorand!)
It's a little hard to say because we can't see <ImageBackground> component or what it does... But if it works like an <img> component we can do something like this.
Use a selector on the image component through its role / alt text / data-testid:
const { getByDataTestId } = render(<Background background={background}>
</Background>);
Then look for an attribute on that component:
expect(getByDataTestId('background')).toHaveAttribute('src', '../../../../assets/images/image.jpg')
When I used getByAltText and getByDataTestId I got is not a function error.
So what worked for me was:
const imgSource = require('../../../../assets/images/image.jpg');
const { queryByTestId } = render(<MyComponent testID='icon' source={imgSource}/>);
expect(queryByTestId('icon').props.source).toBe(imgSource);
I use #testing-library/react-native": "^7.1.0
I ran into this issue today and found that if your URI is a URL and not a required file, stitching the source uri onto the testID works nicely.
export const imageID = 'image_id';
...
<Image testID={`${imageID}_${props.uri}`} ... />
Test
import {
imageID
}, from '.';
...
const testURI = 'https://picsum.photos/200';
const { getByTestId } = render(<Component uri={testURI} />);
expect(getByTestId()).toBeTruthy();
I think that you are looking for:
const uri = 'http://example.com';
const accessibilityLabel = 'Describe the image here';
const { getByA11yLabel } = render (
<Image
source={{ uri }}
accessibilityLabel={accessibilityLabel}
/>
);
const imageEl = getByA11yLabel(accessibilityLabel);
expect(imageEl.props.src.uri).toBe(uri);
I'm trying to write an application that allow the user to select a region of the screen (like selecting to take a screen shot).
Is that even possible?
To specifically take a full screen shot, use the following code (example pulled from Electron Demo App). You can build off of this example, and use the screen, desktopCapturer and rectangle modules in the electron api to customize the code to get a specific screen/display, or select a specific bounding box (x/y coordinates and pixel area).
const electron = require('electron')
const desktopCapturer = electron.desktopCapturer
const electronScreen = electron.screen
const shell = electron.shell
const fs = require('fs')
const os = require('os')
const path = require('path')
const screenshot = document.getElementById('screen-shot')
const screenshotMsg = document.getElementById('screenshot-path')
screenshot.addEventListener('click', function (event) {
screenshotMsg.textContent = 'Gathering screens...'
const thumbSize = determineScreenShotSize()
let options = { types: ['screen'], thumbnailSize: thumbSize }
desktopCapturer.getSources(options, function (error, sources) {
if (error) return console.log(error)
sources.forEach(function (source) {
if (source.name === 'Entire screen' || source.name === 'Screen 1') {
const screenshotPath = path.join(os.tmpdir(), 'screenshot.png')
fs.writeFile(screenshotPath, source.thumbnail.toPng(), function (error) {
if (error) return console.log(error)
shell.openExternal('file://' + screenshotPath)
const message = `Saved screenshot to: ${screenshotPath}`
screenshotMsg.textContent = message
})
}
})
})
})
function determineScreenShotSize () {
const screenSize = electronScreen.getPrimaryDisplay().workAreaSize
const maxDimension = Math.max(screenSize.width, screenSize.height)
return {
width: maxDimension * window.devicePixelRatio,
height: maxDimension * window.devicePixelRatio
}
}
Other ways you could go about this are:
Use object.getClientRects() in the DOM to specify specific elements you want to capture, although this would require foreknowledge of what they are.
Add event listeners in your view to 'draw' the shape of what you want with mouseClick, mouseMove, etc. This stack overflow question has answers which could be adapted to fit what you want to do.
I doubt you are still looking for a solution to this, but after digging i have found a way to do it using a combination of shelljs and clipboard.
const userDataPath = (app).getPath(
'userData'
)
const useP = path.join(userDataPath, 'uploads')
let randomTmpfile = uniqueFilename(useP, 'prefix')
shelljs.exec(`screencapture -ic ${randomTmpfile}.png`, function (res) {
const image = clipboard.readImage('png').toDataURL()
})
I have a winJS app that is a working launcher for a steam game. I'd like to get it to cycle through 5 images even while not running.
It uses only the small tile — there are no wide tiles images for this app.
Here's the code:
(function () {
"use strict";
WinJS.Namespace.define("Steam", {
launch: function launch(url) {
var uri = new Windows.Foundation.Uri(url);
Windows.System.Launcher.launchUriAsync(uri).then(
function (success) {
if (success) {
// File launched
window.close();
} else {
// File launch failed
}
}
);
}
});
WinJS.Namespace.define("Tile", {
enqueue: function initialize() {
var updaterHandle = Windows.UI.Notifications.TileUpdateManager.createTileUpdaterForApplication();
updaterHandle.enableNotificationQueue(true);
return updaterHandle;
},
update: function update () {
var template = Windows.UI.Notifications.TileTemplateType.tileSquareImage;
var tileXml = Windows.UI.Notifications.TileUpdateManager.getTemplateContent(template);
var randIndx = Math.floor(Math.random() * 5);
var randUpdatetime = 1000 * 3 * (((randIndx == 0) ? 1 : 0) + 1); // let the base image stay longer
var tileImageAttributes = tileXml.getElementsByTagName("image");
tileImageAttributes[0].setAttribute("src", "ms-appx:///images/Borderlands2/borderlands_2_" + randIndx + "_sidyseven.png");
tileImageAttributes[0].setAttribute("alt", "Borderlands 2");
var tileNotification = new Windows.UI.Notifications.TileNotification(tileXml);
var currentTime = new Date();
tileNotification.expirationTime = new Date(currentTime.getTime() + randUpdatetime);
tileNotification.tag = "newTile";
var updater = Tile.enqueue();
updater.update(tileNotification);
setTimeout('Tile.update();', randUpdatetime);
}
});
WinJS.Binding.optimizeBindingReferences = true;
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
setTimeout('Steam.launch("steam://rungameid/49520");', 800);
args.setPromise(WinJS.UI.processAll().then(function () {
return WinJS.Navigation.navigate("/default.html", args).then(function () {
Tile.update();
});
}));
}
};
app.start();
})();
Notes:
The code currently does not cycle the image, instead either
apparently never changing, or after launch replacing the application
name text with a tiny view of the default image. This reverts to the
text after a short time, and the cycle may repeat. It never shows a
different image (neither in the small image it erroneously shows, nor
in the main tile).
When I run in debug and set a breakpoint at the
TileUpdater.update(TileNotification) stage, I can verify in the
console that the image src attribute is set to a random image
just as I wanted:
>>>>tileNotification.content.getElementsByTagName("image")[0].getAttribute("src")
"ms-appx:///images/Borderlands2/borderlands_2_4_sidyseven.png"
But this never actually displays on the tile.
These image files are included in the solution, and they appear in the proper directory in the Solution Explorer.
If the image src attribute is set properly in debug then the image may not have the proper "Build Action".
In the 'Properties' of each image, set "Build Action" to "Resource".