Using AlpineJS $persist in JavaScript instead of HTML - javascript

Through this article I learned that you can access these magic variables through JS as well (e.g. in HTML <div x-ref="navbarCollapse">...</div> and in JS this.$refs.navbarCollapse).
I was wondering whether that would work as well with the Persist plugin. The goal is to store a boolean whether the user accepted the Cookie Banner.
So I tried setting the variable as if it were placed in x-data, which looks like:
export default () => ({
open: this.$persist(false),
});
but that throws:
module.esm.js:1656 Alpine Expression Error: Illegal invocation
Expression: "open"
Next I tried placing in my init() function (which is async because I use GraphQL in there)
export default () => ({
open: false,
async init() {
this.open = this.$persist(false);
...
}
});
but then nothing happens.
For more context here is my currently working example with my own functions for reading/writing to local-storage.
import client from './graphql';
import {
gql,
} from '#apollo/client/core';
import {
storageGet,
storageHas,
storageSet,
} from './local-storage';
const COOKIE_CONSENT_NAME = 'cookieConsentAccepted';
export default () => ({
open: false,
enabled: false,
async init() {
// Get all relevant information from the API
const response = await client.query(
{
query: gql`
{
globalSets(handle: "globalsCookieBanner") {
... on globalsCookieBanner_GlobalSet {
cookieBannerStatus
}
}
}
`,
});
const data = response.data.globalSets[0];
this.enabled = data.cookieBannerStatus;
// Check whether to open cookie banner
if (this.enabled && (!storageHas(COOKIE_CONSENT_NAME) || (storageHas(COOKIE_CONSENT_NAME) && storageGet(COOKIE_CONSENT_NAME === false)))) {
this.open = true;
}
},
toggle() {
this.open = !this.open;
storageSet(COOKIE_CONSENT_NAME, !this.open);
},
});

Related

How to use an async function as the second parameter to jest.mock?

I need to use jest.mock together with async, await and import() so that I can use a function from another file inside the mocked module. Otherwise I must copy and paste a few hundreds of slocs or over 1000 slocs, or probably it is not even possible.
An example
This does work well:
jest.mock('./myLin.jsx', () => {
return {
abc: 967,
}
});
Everywhere I use abc later it has 967 as its value, which is different than the original one.
This does not work:
jest.mock('./myLin.jsx', async () => {
return {
abc: 967,
}
});
abc seems to not be available.
Actual issue
I need async to be able to do this:
jest.mock('~/config', async () => {
const { blockTagDeserializer } = await import(
'../editor/deserialize' // or 'volto-slate/editor/deserialize'
);
// … here return an object which contains a call to
// blockTagDeserializer declared above; if I can't do this
// I cannot use blockTagDeserializer since it is outside of
// the scope of this function
}
Actual results
I get errors like:
TypeError: Cannot destructure property 'slate' of '((cov_1viq84mfum.s[13]++) , _config.settings)' as it is undefined.
where _config, I think, is the ~/config module object and slate is a property that should be available on _config.settings.
Expected results
No error, blockTagDeserializer works in the mocked module and the unit test is passed.
The unit test code
The code below is a newer not-working code based on this file on GitHub.
import React from 'react';
import renderer from 'react-test-renderer';
import WysiwygWidget from './WysiwygWidget';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
const mockStore = configureStore();
global.__SERVER__ = true; // eslint-disable-line no-underscore-dangle
global.__CLIENT__ = false; // eslint-disable-line no-underscore-dangle
jest.mock('~/config', async () => {
const { blockTagDeserializer } = await import(
'../editor/deserialize' // or 'volto-slate/editor/deserialize'
);
const createEmptyParagraph = () => {
return {
type: 'p',
children: [{ text: '' }],
};
};
return {
settings: {
supportedLanguages: [],
slate: {
elements: {
default: ({ attributes, children }) => (
<p {...attributes}>{children}</p>
),
strong: ({ children }) => {
return <strong>{children}</strong>;
},
},
leafs: {},
defaultBlockType: 'p',
textblockExtensions: [],
extensions: [
(editor) => {
editor.htmlTagsToSlate = {
STRONG: blockTagDeserializer('strong'),
};
return editor;
},
],
defaultValue: () => {
return [createEmptyParagraph()];
},
},
},
};
});
window.getSelection = () => ({});
test('renders a WysiwygWidget component', () => {
const store = mockStore({
intl: {
locale: 'en',
messages: {},
},
});
const component = renderer.create(
<Provider store={store}>
<WysiwygWidget
id="qwertyu"
title="My Widget"
description="My little description."
required={true}
value={{ data: 'abc <strong>def</strong>' }}
onChange={(id, data) => {
// console.log('changed', data.data);
// setHtml(data.data);
}}
/>
</Provider>,
);
const json = component.toJSON();
expect(json).toMatchSnapshot();
});
What I've tried
The code snippets above show partially what I have tried.
I searched the web for 'jest mock async await import' and did not found something relevant.
The question
If jest.mock is not made to work with async, what else can I do to make my unit test work?
Update 1
In the last snippet of code above, the line
STRONG: blockTagDeserializer('strong'),
uses blockTagDeserializer defined here which uses deserializeChildren, createEmptyParagraph (which is imported from another module), normalizeBlockNodes (which is imported from another module) and jsx (which is imported from another module) functions, which use deserialize which uses isWhitespace which is imported from another module and typeDeserialize which uses jsx and deserializeChildren.
Without using await import(...) syntax how can I fully mock the module so that my unit test works?
If you want to dig into our code, please note that the volto-slate/ prefix in the import statements is for the src/ folder in the repo.
Thank you.
I'd advise not doing any "heavy" stuff (whatever that means) in a callback of jest.mock, – it is designed only for mocking values.
Given your specific example, I'd just put whatever the output of blockTagDeserializer('strong') right inside the config:
jest.mock('~/config', () => {
// ...
extensions: [
(editor) => {
editor.htmlTagsToSlate = {
STRONG: ({ children }) => <strong>{children}</strong>, // or whatever this function actually returns for 'strong'
};
return editor;
},
],
// ...
});
This doesn't require anything asynchronous to be done.
If you need this setup to be present in a lot of files, extracting it in a setup file seems to be the next best thing.
I found a solution. I have a ref callback that sets the htmlTagsToSlate property of the editor in the actual code of the module, conditioned by global.__JEST__ which is defined as true in Jest command line usage:
import { htmlTagsToSlate } from 'volto-slate/editor/config';
[...]
testingEditorRef={(val) => {
ref.current = val;
if (val && global.__JEST__) {
val.htmlTagsToSlate = { ...htmlTagsToSlate };
}
}}
Now the jest.mock call for ~/config is simple, there is no need to do an import in it.
I also use this function:
const handleEditorRef = (editor, ref) => {
if (typeof ref === 'function') {
ref(editor);
} else if (typeof ref === 'object') {
ref.current = editor;
}
return editor;
};

Value is not assigning inside then statement

In this js file I have defined this const Device that is the name of the mobile im using. The thing is when I call Device from another js it returns it empty. Why?
import DeviceInfo from 'react-native-device-info';
var Device = ''
DeviceInfo.getDeviceName().then(deviceName => {
Device = deviceName + ' ('+DeviceInfo.getSystemVersion()+')'
});
export default Device;
The reason why your current approach doesn't work is because DeviceInfo.getDeviceName is an asynchronous call that returns a Promise which resolves with the deviceName.
var Device = ''
DeviceInfo.getDeviceName().then(...)
// the call above will not wait before it goes to next line
// so `Device` will stay as empty string and will be exported as such
export default Device
Instead, if you want to re-use this logic in multiple places, I suggest turning this into a function, like in the following example:
import DeviceInfo from 'react-native-device-info';
function getDeviceFullName() {
return DeviceInfo.getDeviceName()
.then(deviceName => {
return `${deviceName} (${DeviceInfo.getSystemVersion()})`
})
}
export default getDeviceFullName
Then, somewhere else, you could call this function like in the following example:
import getDeviceFullName from './getDeviceFullName'
class App extends React.Component {
state = {deviceName: ""}
componentDidMount() {
getDeviceFullName()
.then(deviceName => {
this.setState({ deviceName })
})
.catch(/* handle errors appropriately */)
}
render() {
return this.state.deviceName === ""
? "Loading"
: this.state.deviceName;
}
}
EDIT as OP mentioned something about Formik integration.
Haven't tested this, but something like the following would be my approach.
class MyReactNativeForm extends React.Component {
state = {
initialValues: { email: "johndoe#gmail.com", deviceName: "" }
}
componentDidMount() {
getDeviceFullName()
.then(deviceName => {
this.setState(prevState => {
return {
initialValues: {...prevState.initialValues, deviceName}
}
})
})
.catch(/* handle errors appropriately*/)
}
render() {
return this.state.initialValues.deviceName === ""
? "Loading"
: <Formik initialValues={this.state.initialValues} />
}
Edit: Someone else posted a great answer while I was typing mine up. My answer re-uses the value for Device because that is how the original question worked. Like the approved answer, you don't need to set it and forget it, but instead you can only return a cb/promise that always gets the most recent data.
Two things immediately stand out. Like #mhodges says, you can't reassign a value to a constant. Instead, you should use let.
Your next issue is trying to export the value returned by an asynchronous call. When you import Device from your js file, the export statement executes before you re-assign the value of Device.
What if you exported your async function or a callback instead?
Promise'able:
var Device;
export function getDeviceInfo() {
if (Device) return Promise.resolve(Device);
return DeviceInfo.getDeviceName().then(deviceName => {
Device = deviceName + ' ('+DeviceInfo.getSystemVersion()+')';
return Promise.resolve(Device);
});
}
Usage might look like:
import { getDeviceInfo } from './utils';
getDeviceInfo().then((deviceInfo) => console.log('Got it!', deviceInfo));
Callback'able:
var Device;
export function getDeviceInfo(cb) {
if (Device) return cb(Device);
DeviceInfo.getDeviceName().then(deviceName => {
Device = deviceName + ' ('+DeviceInfo.getSystemVersion()+')';
cb(Device);
});
}
Usage might look like:
import { getDeviceInfo } from './utils';
getDeviceInfo(function(deviceInfo) {
console.log('Got it!', deviceInfo)
});

Javascript - Cannot use import statement outside a module

So I have major problem with importing from one class to another and what I have done is in my "main" class which I call
detailsPage.js
import { DetailsPage } from '../tests/detailsPageObj';
const utils = require("../utils/utils");
const assert = require('../node_modules/chai').assert;
const userData = require('../globalContent.json');
describe('Details page', function () {
const detailsPage = new DetailsPage();
// The details page is accessible by the specified URL
it(`Is defined by the URL: ${userData.url}`, async function () {
await detailsPage.navigate();
});
// Details page has a form and it can be filled out with user data
it('Has a form that can receive user data', async function () {
await detailsPage.fillFormWithUserData(); // If you want, make the user data passable to the method
await utils.click(detailsPage.form.buttons.nextStep);
});
if (detailsPage.hasStockConflict) {
// Details page allows the user to fix conflicts in stocks
it('Enables resolution of stock conflicts', async function () {
// Wait for stock to fully load
await browser.sleep(2000);
await detailsPage.clickAllRemoveButtons();
await detailsPage.clickAllDecreaseButtons();
});
}
// Details page allows the user to proceed to the next stage when all conflicts (if any) has been resolved
it('Allows the user to proceed to the next stage of purchasing', async function () {
const nextStepButton = detailsPage.form.buttons.nextStep;
await utils.elementToBeClickable(nextStepButton);
await utils.click(nextStepButton);
});
});
and what I am trying tod o is to get DetailsPage from another script which is called:
detailsPageObj
import { element, by } from 'protractor';
const utils = require("../utils/utils");
const userData = require('../globalContent.json');
export class DetailsPage {
get pageUtils() {
return {
qtyRegex: /^Sorry.*?(\d+)/
}
}
private get fields() {
return {
email: element(by.id('email')),
firstName: element(by.id('firstName')),
lastName: element(by.id('lastName')),
postalCode: element(by.id('postalCode')),
addressOne: element(by.id('addressOne')),
addressTwo: element(by.id('addressTwo')),
phone: element(by.id('phone')),
businessCustomerCB: element(by.id('isBusinessCustomer')),
company: element(by.id('company')),
GST: element(by.id('gst')),
}
}
private get groups() {
return {
address: element(by.css('div#addressGroup.input-container.showHiddenGroup'));
company: element(by.id('companyGroup')),
}
}
private get modals() {
return {
contactModalLink: element(by.id('contactModalLink')),
cross: element(by.className('modal-cross')),
}
}
private get formButtons() {
return {
nextStep: element(by.id('submitIdentityFormButton')),
mobile: this.mobileFormButtons
}
}
private get mobileFormButtons() {
return {
continue: element(by.id('stock-conflict-continue-button')),
removeOutOfStockItems: element(by.css('button[id="removeOutOfStockItems"]')), // I just assumed that this is a part of the form
}
}
private get productFrameMobileButtons() {
return {
stockControll: element.all(by.className('stock-controller mobile')),
remove: element.all(by.className('btn btn-remove btn-outlined mobile')),
}
}
private get productFrameDesktopButtons() {
return {
stockControll: element.all(by.className('stock-controller desktop')),
remove: element.all(by.className('btn btn-remove btn-outlined desktop')),
}
}
get form() {
return {
fields: this.fields,
groups: this.groups,
buttons: this.formButtons,
modals: this.modals
}
}
get productFrame() {
return {
buttons: {
decrease: element.all(by.className("btn left")).first(),
mobile: this.productFrameMobileButtons,
desktop: this.productFrameDesktopButtons
}
}
}
get errors() {
return {
stockConflict: element(by.className('generic-error-heading')),
}
}
}
and what I am trying to do is in detailsPage.js im trying to import detailsPageObj.js but whenever I am trying to do it I do get SyntaxError: Cannot use import statement outside a module.
What am I doing wrong
I don't know what is your environment like, but I experienced a similar problem where my environment used a full build step for creating the target JS code from my sources (e.g. from TypeScript or from ES6+) to a bundled/plain JS.
But then my test environment did not have any build step. So when I executed the tests, it only understood plain JS, which by default in a node.js environment does not recognize import but only require.
You can use import in your node.js code without a build step, but you need to follow some steps, e.g. rename your file from *.js to *.mjs. More details here.

How can I evaluate Python code in the document context from JavaScript in JupyterLab?

With Jupyter Notebooks, I could have a cell
%%javascript IPython.notebook.kernel.execute('x = 42')
Then, elsewhere in the document a Python code cell with x would show it bound to 42 as expected.
I'm trying to produce something similar with JupyterLab. I understand I'm supposed to write a plugin rather than using ad-hoc JS, and that's fine, but I'm not finding an interface to the kernel similar to the global IPython from notebooks:
import { JupyerLab, JupyterLabPlugin } from '#jupyterlab/application';
const extension: JupyterLabPlugin<void> = {
// ...
requires: [],
activate: (app: JupyterLab) => {
// can I get to Python evaluation through app?
// by adding another class to `requires` above?
}
}
export default extension;
Here's a hacky attempt that "works". Could still use advice if anyone knows where there is a public promise for the kernel being ready, how to avoid the intermediate class, or any other general improvements:
import { JupyterLab, JupyterLabPlugin } from '#jupyterlab/application';
import { DocumentRegistry } from '#jupyterlab/docregistry';
import { INotebookModel, NotebookPanel } from '#jupyterlab/notebook';
import { IDisposable, DisposableDelegate } from '#phosphor/disposable';
declare global {
interface Window {
'execPython': {
'readyState': string,
'exec': (code: string) => any,
'ready': Promise<void>
} | null
}
}
class ExecWidgetExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
createNew(nb: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
if (window.execPython) {
return;
}
window.execPython = {
'readyState': 'waiting',
'exec': null,
'ready': new Promise((resolve) => {
const wait = setInterval(() => {
if (!context.session.kernel || window.execPython.readyState === 'ready') {
return;
}
clearInterval(wait);
window.execPython.readyState = 'ready';
window.execPython.exec = (code: string) =>
context.session.kernel.requestExecute({ code }, true);
resolve();
}, 50);
})
};
// Usage elsewhere: execPython.ready.then(() => execPython.exec('x = 42').done.then(console.log, console.error))
return new DisposableDelegate(() => {});
}
}
const extension: JupyterLabPlugin<void> = {
'id': 'jupyterlab_foo',
'autoStart': true,
'activate': (app: JupyterLab) => {
app.docRegistry.addWidgetExtension('Notebook', new ExecWidgetExtension())
}
};
export default extension;

Is it possible to call a react-360 method from a native Module?

I am working on a VR project which is has 2 user roles, a leader (who sets up and configures a VR session) and clients (who connect to this session).
I am using a Native module to perform a DOM overlay in which several buttons related to session configuration are displayed for the leader. I was wondering if it is possible to call a function within the React360 code directly from a Native Module (i.e. not as a callback as the event would originate from the Native Module)?
This could be a complete anti-pattern, I can't seem to see a way of doing it...
I actually got this working with the following:
In client.js I passed the context to the DOM overlay native module:
const r360 = new ReactInstance(bundle, parent, {
// Add custom options here
fullScreen: true,
cursorVisibility: "visible",
nativeModules: [
// Create an instance of the DOM overlay module and pass the context
ctx => new DashboardModule(ctx, domDashboardContainer)
],
...options,
});
In the dashboard native module :
const eventToOb = (event) => {
const eventOb = {};
for (let key in event) {
const val = event[key];
if (!(lodash.isFunction(val) || lodash.isObject(val))) {
eventOb[key] = val;
}
}
return eventOb;
};
....
constructor(ctx, overlayContainer) {
super('DashboardModule');
...
this._rnctx = ctx;
this._bridgeName = 'BrowserBridge';
}
onButtonClick() {
....
this._emit('nativeButtonClicked', event);
}
_emit(name, event) {
if (!this._rnctx) {
return;
}
const eventOb = eventToOb(event);
this._rnctx.callFunction(this._bridgeName, 'notifyEvent', [name, eventOb]);
}
...
and in my index.js
...
import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
import lodash from 'lodash';
class BrowserBridge {
constructor() {
this._subscribers = {};
}
subscribe(handler) {
const key = String(Math.random());
this._subscribers[key] = handler;
return () => {
delete this._subscribers[key];
};
}
notifyEvent(name, event) {
lodash.forEach(this._subscribers, handler => {
handler(name, event);
});
}
}
const browserBridge = new BrowserBridge();
BatchedBridge.registerCallableModule(BrowserBridge.name, browserBridge);
....
constructor(props) {
super(props);
this.onBrowserEvent = this.onBrowserEvent.bind(this);
...
}
componentWillMount() {
this.unsubscribe = browserBridge.subscribe(this.onBrowserEvent);
}
onBrowserEvent(name, event) {
// Do action on event here
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
}
If there is a better way of doing this please let me know.

Categories

Resources