Where to store selectors in Cypress.io - cypress

I am new to Cypress. What is the best way to avoid hard coding selectors/locators into each spec. In other frameworks we would create a config file with all of the selectors and have the spec reference it.
Scenario: I may have a selector being used in multiple specs. If the selector changes I do not want to have to change it in each spec. I would rather change it in one location only.

You don't need to do any magic. Simply put your selectors into a file, and import it.
// cypress/support/selectors.js
export default {
mySelector: '.my-selector',
mySelector2: '.my-selector-2'
};
// cypress/integration/one.spec.js
import selectors from '../support/selectors.js';
describe('test', () => {
it('test', () => {
cy.get(selectors.mySelector);
});
});

Related

Global dialog implementation with vue

I am need a reusable, global dialog/modal component in my vue application. I need to be able to call it from any component and update its header text, body text and callback function once the dismiss button is pressed on the modal. I have tried importing a custom made dialog component into each component where I plan to use it and I have tried creating a global dialog where the values would be set using a mutable values in a modals vuex modal. This latter did not work because vuex will not store functions as values. I am new to vue and am not too sure how to go about this and absolutely any advice on a good way to go about it would help tremendously.
I did something like that before. The main ingredient is the handling of events through the $root because you can't relay in this scenario on the normal component communication.
// In your Global Modal
<script>
export default {
name: 'GlobalModal',
mounted () {
this.$root.$on('callGlobalModal', () => {
this.dialog = true
})
},
data: () => ({
dialog: false,
}),
}
</script>
Then call it frome anywhere using this.$root.$emit('callGlobalModal')

CKEditor 5 – get editor instances

I am migrating from CKEditor 4.7 to 5.
In CKE4, I would do something like this:
CKEDITOR.replace('text_area');
and then in another JS function I could get the data by
CKEDITOR.instances.text_area.getData().
But it doesn't appear that CKE5 has a function ClassicEditor.instances or something analogous.
I know I can store the editor instance as a global JS variable, but the code I am working with creates the editors in a general function, so I can't just create a global variable since I don't know the name of the editor a priori. There can also be several editors active on the screen at the same time.
Is there no analog in CKE5 to the old instances that would allow me to get an editor instance from just the id of the textarea it replaced?
I guess I could create my own global array to hold the editor instances, but I would rather not if there is something built in and better-supported
This question was already answered in How to get data from CKEDITOR5 instance, but let's consider here a case with more than one editor instance.
I guess I could create my own global array to hold the editor instances, but I would rather not if there is something built in and better-supported
There's no repository of editor instances. We could add it but we don't feel that this is an essential feature. It's actually something that people got used to in CKEditor 4 so they never thought and learned how to manage their editors by themselves.
Also, the reason why there's no single repository of instances is that there's no central singleton object at all. You may implement multiple editor classes and they don't have to know about each other. To come up with a repository, we'd need to centralise these things again.
So, as you pointed out yourself, a simple way to store all instances would be by having a global (global within your app or module, not necessarily a "global JS variable") map of these instances.
The keys to these instances can be the ids of elements on which you initialised editors:
const editors = {}; // You can also use new Map() if you use ES6.
function createEditor( elementId ) {
return ClassicEditor
.create( document.getElementById( elementId ) )
.then( editor => {
editors[ elementId ] = editor;
} )
.catch( err => console.error( err.stack ) );
}
// In real life, you may also need to take care of the returned promises.
createEditor( 'editor1' );
createEditor( 'editor2' );
// Later on:
editors.editor1.getData();
What if you don't assign ids to elements in the DOM? If you use ES6, then it's not a problem. Elements, like other objects, can be keys of a map.
const editors = new Map();
function createEditor( elementToReplace ) {
return ClassicEditor
.create( elementToReplace )
.then( editor => {
editors.set( elementToReplace, editor );
} )
.catch( err => console.error( err.stack ) );
}
// In real life, you may also need to take care of the returned promises.
createEditor( textarea1 );
createEditor( textarea2 );
// Later on:
editors.get( textarea1 ).getData();
If you can't use ES6, then you'd need to do a bit more – e.g. dynamically assign some data-editor-id attributes to elements on which you create the editors.
It's not the first time I'm trying to remind myself how to access the CKEditor instance on a production website having just access to the DOM via the developer console, so a reminder for myself ;)
https://ckeditor.com/docs/ckeditor5/latest/builds/guides/faq.html#how-to-get-the-editor-instance-object-from-the-dom-element
It's possible to access the editor instance using the ckeditorInstance property which is available on the contenteditable element that CKEditor 5 is using. You can access this DOM element via e.g. the .ck-editor__editable class.
// A reference to the editor editable element in the DOM.
const domEditableElement = document.querySelector( '.ck-editor__editable' );
// Get the editor instance from the editable element.
const editorInstance = domEditableElement.ckeditorInstance;
// Now you can use the editor instance API.
editorInstance.setData( '<p>Hello world!<p>' );
Running multiple copies of the editor using jQuery and a class selector:
$( '.editor' ).each( function() {
InlineEditor
.create( this )
.catch( error => {
console.error( error );
} );
});

Ckeditor plugin configuration not working

I have tried to add justify plugin to be able to align text right, left or centre. But after following the instructions in the documentation (http://apostrophecms.org/docs/tutorials/howtos/ckeditor.html), I wonder if the plugin should be located in a specific folder (mine is at public/modules/apostrophe-areas/js/ckeditorPlugins/justify/), as it disappears when the site is loaded, but if I include it in some other folder such as public/plugins/justify still doesn't work.
This is my code just in case: (located at lib/modules/apostrophe-areas/public/js/user.js)
apos.define('apostrophe-areas', {
construct: function(self, options) {
// Use the super pattern - don't forget to call the original method
var superEnableCkeditor = self.enableCkeditor;
self.enableCkeditor = function() {
superEnableCkeditor();
// Now do as we please
CKEDITOR.plugins.addExternal('justify', '/modules/apostrophe-areas/js/ckeditorPlugins/justify/', 'plugin.js');
};
}
});
Also, it would be nice to know how the plugin should be called at the Toolbar settings for editable widgets.
Thanks!
The URL you need is:
/modules/my-apostrophe-areas/js/ckeditorPlugins/justify/
The my- prefix is automatically prepended so that the public folders of both the original apostrophe-areas module and your project-level extension of it can have a distinct URL. Otherwise there would be no way for both to access their user.js, for instance.
I'll add this note to the HOWTO in question, which currently handwaves the issue by stubbing in a made-up URL.
As for how the plugin should be called, use the toolbar control name exported by that plugin — that part is a ckeditor question, not really an Apostrophe one. But looking at the source code of that plugin they are probably JustifyLeft, JustifyCenter, JustifyRight and JustifyBlock.
It turns out that it's not enough to simply call CKEDITOR.plugins.addExternal inside apostophe-areas. You also need to override self.beforeCkeditorInline of the apostrophe-rich-text-widgets-editor module and explicitly call self.config.extraPlugins = 'your_plugin_name';.
Here's what I ended up with:
In lib/modules/apostrophe-areas/public/js/user.js:
apos.define('apostrophe-areas', {
construct: function(self, options) {
// Use the super pattern - don't forget to call the original method
var superEnableCkeditor = self.enableCkeditor;
self.enableCkeditor = function() {
superEnableCkeditor();
// Now do as we please
CKEDITOR.plugins.addExternal('justify', '/modules/my-apostrophe-areas/js/ckeditorPlugins/justify/', 'plugin.js');
};
}
});
then in in lib/modules/apostrophe-rich-text-widgets/public/js/editor.js:
apos.define('apostrophe-rich-text-widgets-editor', {
construct: function(self, options) {
self.beforeCkeditorInline = function() {
self.config.extraPlugins = 'justify';
};
}
});
For some reason doing CKEDITOR.config.extraPlugins = 'justify' inside apostrophe-areas does not work, probably due to the way how CKEDITOR is initialized;
One more thing: this particular plug-in (justify, that is) does not seem to follow the button definition logic. It has button icons defined as images, whereas CKEditor 4.6 used in Apostrophe CMS 2.3 uses font-awesome to display icons. It means that the icons that ship with the justify module won't be displayed and you'll have to write your own css for each button individually.
There is another issue which you'll probably face when you finally enable the justify buttons. The built-in html sanitizer will be strip off the styles justify adds to align the content.
Apostrophe CMS seems to be using sanitize-html to sanitize the input, so changing CKEditor settings won't have any effect. To solve the issue, add the following to your app.js:
'apostrophe-rich-text-widgets': {
// The standard list copied from the module, plus sup and sub
sanitizeHtml: {
allowedAttributes: {
a: ['href', 'name', 'target'],
img: ['src'],
'*': ['style'] //this will make sure the style attribute is not stripped off
}
}
}
Thank you both for your help. After following both approaches of: locating the plugin at my-apostrophe-areas folder as well as editing editor.js on the apostrophe-rich-text widget (the sanitize.html file was already using that configuration), I got the plugin working. However, I was still having the issue with the icons.
I fixed that adding the Font Awesome icons that correspond to align-justify, align-right, align-left and align-center at the end of public/modules/apostrophe-areas/js/vendor/ckeditor/skins/apostrophe/editor.less

How to reset the Flux stores between jasmine tests?

When running tests on stores with karma & jasmine (instead of jest), the store's state persists between tests. What's the best way to reset the state for every test?
With jest, every test is run in its own environment automatically. This post includes instructions for how to clear the require cache between tests for karma (copied below) to imitate jest, but I can't seem to get it to work.
//tests.webpack.js (suggested by linked article, modified to match my project structure)
// Create a Webpack require context so we can dynamically require our
// project's modules. Exclude test files in this context.
'use strict';
var projectContext = require.context('./path/to/js', true, /^(?!\.test).js$/);
// Extract the module ids that Webpack uses to track modules.
var projectModuleIds = projectContext.keys().map(function (module) {
return String(projectContext.resolve(module));
});
beforeEach(function () {
// Remove our modules from the require cache before each test case.
projectModuleIds.forEach(function (id) {
return delete require.cache[id];
});
});
My current file:
tests.webpack.js (current)
var context = require.context('./path/to/js', true, /\.test\.js$/);
context.keys().forEach(context);
Not sure if it helps, but here's a simplified version of the code from my tests that shows the store's state persisting between tests.
MissionStore.test.js
describe('MissionStore', function() {
beforeEach(function() {
this.MissionStore = rewire("./MissionStore");
this.registeredCallback = this.MissionStore.__get__("registeredCallback");
});
// A bunch of initialization tests without data
describe("set data", function(){
beforeEach(function(){
// Sets this.MissionStore.getMissions() to some sample data
this.registeredCallback(fetchMissions);
});
it('modifies mission data', function(){
var mission = this.MissionStore.getMissions()[0];
expect(mission.edit).not.toEqual(true);
// modify mission to add an edit flag = true
this.registeredCallback({
actionType: MissionConstants.MISSION_TOGGLE_EDIT_FLAG,
mission : mission
});
expect(this.MissionStore.getMissions()[0].edit).toEqual(true);
});
it('do something else', function(){
expect(this.MissionStore.getMissions()[0].edit).not.toEqual(true); // Failing test
console.log(this.MissionStore.getMissions()[0]);
// prints {..., edit: true}
});
});
});
In the 'do something else' test, I'm expecting the this.MissionStore state to be reset between tests so edit won't be equal to true anymore. How would I go about doing this?
Your /^(?!\.test).js$/ RegExp supposed to match every file ending with .js but not with .test.js is wrong, it will only ever match .js. It should be /^(?!.*\.test\.js$).*\.js$/. See Negative lookahead Regular Expression.
So my guess is that projectModuleIds is empty, and that you are not invalidating anything between require calls.

separate ckeditor template for each page

I want to have separate config for ckditor.
For example in page temp1.html i want to have 'links' and in page temp2.html i don't want to have links.
Whats the good config for this?
I think configuration in below code is proper place for do this.
//var editor_data = CKEDITOR.instances.editor1.getData();
$('textarea#editor').ckeditor(
function () {
/* callback code */
},
//configuration
{
toolbar: 'Basic',
language: 'en',
});
You can use config.removePlugins to control the presence of certain plugins, like link (also config.removeButtons). But please note that since CKEditor 4.1, by doing this you restrict the content of the editor associated with the plugin or button (no link plugin or button = no links in the content).
So if you want to share the same content between different templates which use a different sets of plugins you need to explicitly expand config.extraAllowedContent of some editors:
$('#editor-linkless').ckeditor( function() {}, {
removePlugins: 'link',
extraAllowedContent: 'a[href,name,id,target]'
} );
$('#editor-regular').ckeditor();
JSFiddle.
See the official guide about ACF. Also this answer to know more.

Resources