Testing Components
To test Joystick components, the @joystick.js/test
package includes a special test.render()
method for rendering your component's into memory. Once rendered into memory, you can test:
- That, given some props and user data, the component renders the correct HTML.
- DOM event listeners behave in the way that you expect.
- Component methods you've defined behave in the way that you expect.
- Data you fetch on the component matches your expectations.
By testing in these four areas, you can gain confidence that your UI behaves how you'd expect for your users.
Using the test.render() method
To generate an in-memory instance of your component for testing, call the test.render()
method from @joystick.js/test
, passing the path of the component you'd like to test:
/tests/ui/pages/index/index.test.js
import test from '@joystick.js/test';
test.that('the index page renders as expected', async (assert = {}) => {
const component = await test.render('ui/pages/index/index.js', {
props: {},
state: {},
options: {
language: 'en-US',
},
});
assert.is(true, true);
});
Above, we've defined a test with the test.that()
method and inside of the test's callback, we create a variable component
and set it to a call to test.render()
(we expect a Promise to be returned so we place async
on the callback for our test and prefix our call with await
).
To test.render()
, as the first argument, we pass the path to the Joystick component we'd like to test: ui/pages/index/index.js
. As the second argument, we pass an options object containing the default props
, default state
, and some render options
(here we force the language
for this test to be en-US
).
In response, we expect to get back an object with a few methods to help us test our component.
Testing Rendered HTML
On the object returned by test.render()
, we can retrieve the rendered HTML for the component by calling to component.render_to_html()
on the component instance object returned by test.render()
:
/tests/ui/pages/index/index.test.js
import test from '@joystick.js/test';
test.that('the index page renders as expected', async (assert = {}) => {
const component = await test.render('ui/pages/index/index.js', {
props: {},
state: {},
options: {
language: 'en-US',
},
});
const html = component.render_to_html();
assert.is(html?.includes('A full-stack JavaScript framework for building web apps and websites.'), true);
});
When we call to component.render_to_html()
we expect to get back a string of HTML returned from calling the component's render()
method internally.
Testing DOM events
When we think about testing DOM events on components in Joystick, what we really want to focus on is testing side effects. So, instead of asking "was the DOM event triggered?" we want to ask "did triggering the DOM event do what I expected it to do in my app?" For example, if I have a click
event on a button
in my component that's supposed to set the current time on state, I want to trigger that event and then confirm that the current time was set on the component's state.
/tests/ui/pages/index/index.test.js
import test from '@joystick.js/test';
test.that('the index page renders as expected', async (assert = {}) => { ... });
test.that('a button click sets the current time on state', async (assert = {}) => {
const component = await test.render('ui/pages/index/index.js', {
props: {},
state: {},
options: {
language: 'en-US',
},
});
component.test.event('click', 'button');
assert.is(!!component.state.current_time, true);
});
Above, we assume that our component has an event listener defined click button
listening for a click
event on a button
element in our component. Inside of that event listener, we expect a call to instance.set_state({ current_time: new Date().getTime() })
. For our test, we just want to confirm that something was set on state.current_time
so we do a double-bang check on the value to assert that it exists.
Testing component methods
Depending on the implementation of our component, we may have one or more custom methods defined to help us perform work elsewhere on the component. Using the component.test.method()
function, we can call individual methods directly on the component instance to unit test them:
/tests/ui/pages/index/index.test.js
import test from '@joystick.js/test';
test.that('the reverse_name() method reverses the name', async (assert = {}) => { ... });
test.that('a button click sets the current time on state', async (assert = {}) => { ... });
test.that('the reverse_name() method reverses the name', async (assert = {}) => {
const component = await test.render('ui/pages/index/index.js', {
props: {},
state: {},
options: {
language: 'en-US',
},
});
const reversed_name = component.test.method('reverse_name', ['Ryan']);
assert.is(reversed_name === 'nayR', true);
});
Above, we anticipate a method reverse_name()
that takes in a name as a string and reverses it. To perform the test, we utilize the component.test.method()
function which takes in the name of the method we want to call as a string
for the first argument and an array of arguments to pass to the method as an array
(the array contents are spread on the call to the method like method_to_call(...method_args)
behind the scenes).
In response, we expect the return value of the method and then compare the result to our expectation to run our assertion.
Testing data
If a component utilizes the data function to fetch data when rendered on the server, it can be helpful to verify that the data you expect to be returned is actually returned. Utilizing the component.test.data()
method, we can verify we're getting back the data we expect:
/tests/ui/pages/index/index.test.js
import test from '@joystick.js/test';
test.that('the reverse_name() method reverses the name', async (assert = {}) => { ... });
test.that('a button click sets the current time on state', async (assert = {}) => { ... });
test.that('the reverse_name() method reverses the name', async (assert = {}) => { ... });
test.that('the data returned matches our expectations', async (assert = {}) => {
const component = await test.render('ui/pages/index/index.js', {
props: {},
state: {},
options: {
language: 'en-US',
},
});
const data = await component.test.data();
assert.is(typeof data?.stars === 'integer', true);
});
Above, we call to test.render()
to create our component instance then call the component.test.data()
method off of that instance. Because we expect our data
function to return a Promise (due to its implementation), we await
the call to component.test.data()
. Next, we verify that the data
value we received back has a stars
field equal to an integer (the default Joystick index page fetches the current Github stars for the project repo).
API Reference
test.render()
Function API
Function API
test.render(component_path: string, render_options: object) => object;
Arguments
-
component_path string Required
The path to a component in the project to render into memory.
-
render_options object
An object of options for customizing the render.
-
props object
Props to pass to the rendered component instance.
-
state object
State to pass to the rendered component instance.
-
options object
Additional options to customize the behavior of the rendered component.
-
language string
A string containing the ISO language code to load for the test.
-
user object
A user object returned by the
test.accounts.signup()
method containing a user session to perform the test against.
-
-
layout string
The path to a Joystick layout component to render the component into.
-
Return Value
Object API
{
dom: object,
instance: object,
test: {
data: function,
render_to_html: function,
method: function,
event: function,
}
}
Return Value Parameters
-
component_path object Required
The in-memory DOM instance generated by linkedom (the library used internally for creating an in-memory DOM).
-
instance object
The Joystick component instance.
-
test object
An object containing helper methods for writing component tests.
-
data function
A function for calling the
data()
method on the component instance. Accepts an optionalinput
object containing input values for API requests.-
input object
An optional object containing key/value pairs to pass as input to the data function.
-
-
render_to_html function
A function for rendering the component's
render()
method return value to an HTML string. -
method function
A function for testing custom methods on the component instance.
-
method_name string Required
The name of the method on the component to call.
-
method_args array
An optional array containing arguments to pass to the method when it's called. Arguments are spread on the call (i.e., the index 0 value is the first argument, the index 1 value is the second argument, etc.).
-
-
event function
A function for triggering a DOM event on the component instance. Receives an
event_type
,event_target
, and anyoverrides
for the DOM event object.-
event_type string Required
A valid DOM event name (e.g.,
click
ormouseover
) to trigger on theevent_target
. -
event_target string Required
The CSS/DOM selector for the element receiving the DOM event of
event_type
.
-
-