How to grab a container when wrapping render call into act? - react-testing-library

render utility of React Testing Library among other things returns a container, that can be used to manually query the constructed DOM snippet. However when render call is wrapped into an act utility (to avoid dreadful When testing, code that causes React state updates should be wrapped into act(...) warnings), return value is undefined. I find this not intuitive. Is it expected? How would one gain access to the container (and other utilities) when using act?

You can access all the destructured values by declaring them as a let variable outside your test and then using them in your render statement:
let debug;
let container;
it('renders', async () => {
await act( async () => ({ container, debug } = render(<MyComponent />)));
debug(container);
});

Related

React testing library: how to explicitly reuse rendered component without removing auto-cleanup

RTLib has auto-cleanup and I want to keep it.
In some cases though, I want to reuse the render result of my component in that way (tests are simplified):
describe('some set of related functionality', () => {
const onSelect = jest.fn();
const Wrapper = render(
<MyComponent onSelect={onSelect)} />
);
afterEach(() => {
onSelect.mockReset();
});
it('tests something', async () => {
userEvent.click(await Wrapper.findByText('some-text'));
expect(onSelect).toBeCalledWith('something');
});
it('also tests something on the same component very related to closest describe block', async () => {
userEvent.click(await Wrapper.findByText('some-other-text'));
expect(onSelect).toBeCalledWith('some-other-thing');
});
});
so the idea here is to reuse the Wrapper between some tests and query over that wrapper instead of the global screen which is cleaned in global afterEach.
I like the default behaviour but I think it might be useful to reuse the wrapper between some tests, e.g. to speed up some tests or make them shorter.
The alternative, for now, is to write many assertions (many tests in fact) in a single it statement. E.g. it can be like this instead
it('tests some set of related functionality', () => {
const onSelect = jest.fn();
render(<MyComponent onSelect={onSelect)} />);
// tests something
userEvent.click(await Wrapper.findByText('some-text'));
expect(onSelect).toBeCalledWith('something');
// have to do it manually now
onSelect.mockReset();
// also tests something on the same component
userEvent.click(await Wrapper.findByText('some-other-text'));
expect(onSelect).toBeCalledWith('some-other-thing');
});
The motivation here is:
Tests speed
An ability to write a sequence of tests where every step is a test case by itself, without the need to repeat render code and previous steps (i.e. user interactions) to reach a certain component state.
Is there any way to achieve that?
This question is a bit subjective, but as the creator of the library you're using you may be interested in my opinion (especially since you emailed me a link to this) 😉
The alternative you mention is the recommended approach. Read more in Write fewer, longer tests.
Also, I noticed you're calling the return value of render Wrapper which is old cruft from enzyme and I recommend avoiding that as well. Read more about that in Common mistakes with React Testing Library.
I hope that's helpful.

Is there a fix to highlight subsequent DOM changes using a Chrome Extension that currently only reads source code (+highlights keywords) on page load? [duplicate]

Essentially I want to have a script execute when the contents of a DIV change. Since the scripts are separate (content script in the Chrome extension & webpage script), I need a way simply observe changes in DOM state. I could set up polling but that seems sloppy.
For a long time, DOM3 mutation events were the best available solution, but they have been deprecated for performance reasons. DOM4 Mutation Observers are the replacement for deprecated DOM3 mutation events. They are currently implemented in modern browsers as MutationObserver (or as the vendor-prefixed WebKitMutationObserver in old versions of Chrome):
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var observer = new MutationObserver(function(mutations, observer) {
// fired when a mutation occurs
console.log(mutations, observer);
// ...
});
// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
subtree: true,
attributes: true
//...
});
This example listens for DOM changes on document and its entire subtree, and it will fire on changes to element attributes as well as structural changes. The draft spec has a full list of valid mutation listener properties:
childList
Set to true if mutations to target's children are to be observed.
attributes
Set to true if mutations to target's attributes are to be observed.
characterData
Set to true if mutations to target's data are to be observed.
subtree
Set to true if mutations to not just target, but also target's descendants are to be observed.
attributeOldValue
Set to true if attributes is set to true and target's attribute value before the mutation needs to be recorded.
characterDataOldValue
Set to true if characterData is set to true and target's data before the mutation needs to be recorded.
attributeFilter
Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed.
(This list is current as of April 2014; you may check the specification for any changes.)
Edit
This answer is now deprecated. See the answer by apsillers.
Since this is for a Chrome extension, you might as well use the standard DOM event - DOMSubtreeModified. See the support for this event across browsers. It has been supported in Chrome since 1.0.
$("#someDiv").bind("DOMSubtreeModified", function() {
alert("tree changed");
});
See a working example here.
Many sites use AJAX/XHR/fetch to add, show, modify content dynamically and window.history API instead of in-site navigation so current URL is changed programmatically. Such sites are called SPA, short for Single Page Application.
Usual JS methods of detecting page changes
MutationObserver (docs) to literally detect DOM changes.
Info/examples:
How to change the HTML content as it's loading on the page
Performance of MutationObserver to detect nodes in entire DOM.
Lightweight observer to react to a change only if URL also changed:
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
onUrlChange();
}
}).observe(document, {subtree: true, childList: true});
function onUrlChange() {
console.log('URL changed!', location.href);
}
Event listener for sites that signal content change by sending a DOM event:
pjax:end on document used by many pjax-based sites e.g. GitHub,
see How to run jQuery before and after a pjax load?
message on window used by e.g. Google search in Chrome browser,
see Chrome extension detect Google search refresh
yt-navigate-finish used by Youtube,
see How to detect page navigation on YouTube and modify its appearance seamlessly?
Periodic checking of DOM via setInterval:
Obviously this will work only in cases when you wait for a specific element identified by its id/selector to appear, and it won't let you universally detect new dynamically added content unless you invent some kind of fingerprinting the existing contents.
Cloaking History API:
let _pushState = History.prototype.pushState;
History.prototype.pushState = function (state, title, url) {
_pushState.call(this, state, title, url);
console.log('URL changed', url)
};
Listening to hashchange, popstate events:
window.addEventListener('hashchange', e => {
console.log('URL hash changed', e);
doSomething();
});
window.addEventListener('popstate', e => {
console.log('State changed', e);
doSomething();
});
P.S. All these methods can be used in a WebExtension's content script. It's because the case we're looking at is where the URL was changed via history.pushState or replaceState so the page itself remained the same with the same content script environment.
Another approach depending on how you are changing the div.
If you are using JQuery to change a div's contents with its html() method, you can extend that method and call a registration function each time you put html into a div.
(function( $, oldHtmlMethod ){
// Override the core html method in the jQuery object.
$.fn.html = function(){
// Execute the original HTML method using the
// augmented arguments collection.
var results = oldHtmlMethod.apply( this, arguments );
com.invisibility.elements.findAndRegisterElements(this);
return results;
};
})( jQuery, jQuery.fn.html );
We just intercept the calls to html(), call a registration function with this, which in the context refers to the target element getting new content, then we pass on the call to the original jquery.html() function. Remember to return the results of the original html() method, because JQuery expects it for method chaining.
For more info on method overriding and extension, check out http://www.bennadel.com/blog/2009-Using-Self-Executing-Function-Arguments-To-Override-Core-jQuery-Methods.htm, which is where I cribbed the closure function. Also check out the plugins tutorial at JQuery's site.
In addition to the "raw" tools provided by MutationObserver API, there exist "convenience" libraries to work with DOM mutations.
Consider: MutationObserver represents each DOM change in terms of subtrees. So if you're, for instance, waiting for a certain element to be inserted, it may be deep inside the children of mutations.mutation[i].addedNodes[j].
Another problem is when your own code, in reaction to mutations, changes DOM - you often want to filter it out.
A good convenience library that solves such problems is mutation-summary (disclaimer: I'm not the author, just a satisfied user), which enables you to specify queries of what you're interested in, and get exactly that.
Basic usage example from the docs:
var observer = new MutationSummary({
callback: updateWidgets,
queries: [{
element: '[data-widget]'
}]
});
function updateWidgets(summaries) {
var widgetSummary = summaries[0];
widgetSummary.added.forEach(buildNewWidget);
widgetSummary.removed.forEach(cleanupExistingWidget);
}

Office JavaScript API: selecting a range in Word for Mac

I'm working on a side project using the Microsoft Office JavaScript APIs. I have some functionality working to select a range in order to scroll to a particular position within a document. This works as expected in Office for the web, but in Office for Mac I get the following error when calling context.sync().then():
Unhandled Promise Rejection: RichApi.Error: ItemNotFound
I can't find any documentation on that particular error, and I'm not sure how to troubleshoot what I might be doing wrong. What am I missing? Like I said, this works in the web interface.
Here is minimal sample of code that demonstrates the problem:
function UI(context) {
this.context = context;
}
UI.prototype.initialize = function() {
var paragraphs = this.context.document.body.paragraphs;
this.context.load(paragraphs);
document.querySelector('button').addEventListener('click', () => {
this.context.sync().then(() => {
this.goToRange(paragraphs.items[0]);
});
});
};
UI.prototype.goToRange = function(range) {
range.select();
this.context.sync();
};
document.addEventListener('DOMContentLoaded', () => {
Office.onReady(() => {
Word.run(context => {
return context.sync().then(() => {
new UI(context).initialize();
});
});
});
});
The only thing I can think of is that maybe the reference to the paragraph client object becomes "stale" in some sense, perhaps based on some resource limits that are lower in the Mac application than in the online interface? (That would be counterintuitive to me, but it's the only thing that comes to mind.)
I think I figured out the problem. I stumbled upon a hint while putting together the minimum code sample in the question; I removed a little too much code at first and encountered the following error:
Unhandled Promise Rejection: RichApi.Error: The batch function passed
to the ".run" method didn't return a promise. The function must return
a promise, so that any automatically-tracked objects can be released
at the completion of the batch operation.
I believe the issue is that, at least in Word for Mac, you can't use the context object provided by Word.run in an asynchronous event listener. I'm guessing this is because, as the above error states, some state has been released after resolving the promise returned. I can get the code to work by adding a dedicated call to Word.run (and using the fresh context provided) inside the event listener.
It is still a little odd that it works just fine in the browser. Presumably, the same state is not released as aggressively in the browser-based version.

Setting callback for SEditableText widget to save text

I have implemented an SEditableText widget in my editor plugin but I can't figure out a reasonable way of accessing the value in the widget. I know there is an SEditableText.OnTextChanged() function but I don't see anyway to override it or set a callback of my own. Is there a standard way to save the content of an SEditableText to a variable?
I am working in the context of FModeToolKit, not sure if that makes a difference.
When you instantiate the slate widget, you should already be making a call to SNew. As part of the property initialiser chain list, initialise SEditableText::OnTextChanged and bind to your container UObject, using BIND_UOBJECT_DELEGATE. The delegate signature should be FOnTextChanged. Your handler will receive an FText which may be stored in a variable as required.
Example:
SEditableText EditableText = SNew(SEditableText)
.OnTextChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, OnTextChanged);
then:
void UMyUObject::HandleOnTextChanged(const FText& InText)
{
// Do something with text
}
Take a look at it in action in UEditableText::RebuildWidget (EditableText.cpp:50, v4.22.1).
NB. I know you specifically asked about OnTextChanged, but also consider OnTextComitted; this is only fired when the slate widget loses focus.
Ah; BIND_UOBJECT_DELEGATE is in UMG. The macro is however just a helper to create a UObject to which you may bind:
#define BIND_UOBJECT_DELEGATE(Type, Function) \
Type::CreateUObject( this, &ThisClass::Function )
So there's two options, either use <my type>::CreateUObject, or you could bind via a shared pointer. Off the top of my head, SP method should look something like:
Edit (based on comments):
If you're not linked against UMG, you could try manually creating the UObject based on the macro defined in SlateWrapperTypes in UMG:
#define BIND_UOBJECT_DELEGATE(Type, Function) \
Type::CreateUObject( this, &ThisClass::Function )
To instead use a shared pointer, the syntax should be along the following lines:
auto OnTextChangedSP = FOnTextChanged::CreateSP(this, &MyClass::MyOnTextChangedHandler);
SEditableText EditableText = SNew(SEditableText)
.OnTextChanged(OnTextChangedSP);

Set execution order of event-handlers of click event in jQuery?

I am using jQuery 1.9.1.
Suppose i have a button with id="clickMe"
My jQuery code is:
$('#clickMe').click(function(event)
{
eventHandler1();//do something
eventHandler2();//use output from eventHandler1() and do something
}
Now, i want "eventHandler2" to be executed at last so that i could use the output of "eventHandler1". Is there any way to do this manually and not just the way i have put the handlers inside the click event?
One more thing, "eventHandler1()" and "eventHandler2()" are present in different .js files and thus the requirement.
jQuery.when() provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events.
For example, when the Deferreds are jQuery.ajax() requests, the arguments will be the jqXHR objects for the requests, in the order they were given in the argument list.
$.when(eventHandler1).then(eventHandler2).done(function(){
alert('done.');
});
So can even use GLOBAL variable to store eventHandler1 output and access that inside eventHandler2
Example
var someVar;
function eventHandler1()
{
// process
someVar = some value from process
return someVar;
}
function eventHandler2()
{
alert(someVar);
}
Response to OP comment
as you have asked about execute handler in queue you can use Jai answer.
you can use .when .then and .done as below.
$.when(eventHandler1).then(eventHandler2).done(function(){
//process code
});