aurelia test view change not reflected in viewmodel - jasmine

i have a basic custom element.
//my-component.html
<template>
<input type="text" id="my-input" value.bind="searchText">
</template>
//my-component.js
export class MyComponent {
searchText = 'initial text';
}
what i am trying to assert in my unit test is that when i update the value of my input field (id=my-input) in the dom the correct bound value (searchText) is also updated.
heres my test (jasmine/karma)
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
describe('some desc', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('src/my-component')
.inView('<div><my-component></my-component></div>');
});
it('some it', done => {
component.create(bootstrap).then(() => {
const inpuElement = document.querySelector('#my-input');
inpuElement.value = 'new text';
//component.viewModel.searchText = 'new text';
expect(component.viewModel.searchText).toBe('new text');
done();
}).catch(e => { console.log(e.toString()) });
});
afterEach(() => {
component.dispose();
});
});
when i run this the test fails with
Expected 'initial text' to be 'new text'
to get it passing i need to replace
component.viewModel.searchText = 'new text';
with
inpuElement.value = 'new text';
i really dont want to do this as i am trying to mimic the user inputing text directly into the input field.
can this be done and if so what am i missing?

I guess you are looking for something like this: https://github.com/aurelia/testing/issues/70
Aurelia is observing the DOM events to update the VM. As you do not trigger any event, the VM is not "notifying" the change. Do something like this (assuming you use aurelia-pal):
import {StageComponent} from 'aurelia-testing';
import {bootstrap} from 'aurelia-bootstrapper';
import {DOM} from 'aurelia-pal';
describe('some desc', () => {
let component;
beforeEach(() => {
component = StageComponent
.withResources('src/my-component')
.inView('<div><my-component></my-component></div>');
});
it('some it', done => {
component.create(bootstrap).then(() => {
const inputElement = DOM.getElementById('my-input');
inputElement.value = 'new text';
inputElement.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(component.viewModel.searchText).toBe('new text');
done();
}, 50);
}).catch(e => { console.log(e.toString()) });
});
afterEach(() => {
component.dispose();
});
});

Related

How to preserve login in cypress e2e tests?

I have implemented some cypress e2e tests.
But, before each, it() block it performs login step.
I want to make login once for every test suit.(for decreasing tests run time)
my tests structure is as below :
describe('Main Suit', () => {
before(() => {
cy.visit('/register')
// Steps to register
})
beforeEach(() => {
cy.visit('/login')
cy.get('#email').type('test12#gmail.com');
cy.get('#password').type('password')
cy.get('.p-button').click()
cy.wait(2000)
})
describe('test suit - 1', () => {
it('test - 1', () => {
cy.visit('/somePath')
cy.get('table').contains('td', 'No data found.');
cy.wait(2000)
})
it('test - 2', () => {
cy.visit('/somePath')
cy.get('table').contains('td', 'No data found.');
cy.wait(2000)
})
it('test - 3', () => {
cy.visit('/somePath')
cy.get('table').contains('td', 'No data found.');
cy.wait(2000)
})
});
describe('test suit - 2', () => {
it('test - 1', () => {
cy.visit('/somePath')
cy.get('table').contains('td', 'No data found.');
cy.wait(2000)
})
it('test - 2', () => {
cy.visit('/somePath')
cy.get('table').contains('td', 'No data found.');
cy.wait(2000)
})
it('test - 3', () => {
cy.visit('/somePath')
cy.get('table').contains('td', 'No data found.');
cy.wait(2000)
})
});
});
I have tried cy.session().
But, It did not worked.
my cypress version is ^10.4.0
Thanks in advance.
The cy.session() command is designed to do exactly what you want.
If it did not work, perhaps you are using it incorrectly?
A common mistake is to call it just once. Instead it must be in beforeEach(), but it does not perform the login for each test - the first test it will run setup(), for each other test it will just restore the login credentials (either cookies, localstorage, or session storage).
beforeEach(() => {
const setup = () => {
cy.visit('/login')
cy.get('#email').type('test12#gmail.com');
cy.get('#password').type('password')
cy.get('.p-button').click()
})
cy.session('login', setup())
})
Thanks all for help.
After digging deep.
I have found the solution.
I have added below statement under cypress.config.js file under e2e block :
experimentalSessionAndOrigin: true,
And added below block of code before all test suits :
beforeEach(() => {
cy.session('user', () => {
cy.visit('/login');
cy.get('#email').type('test12#gmail.com');
cy.get('#password').type('password');
cy.get('.p-button').click();
cy.wait(2000);
});
});

use beforeEach on each test

I need to call a function cy.restoreLocalStorage(); on each describe in Cypress to restore my local storage for the test:
describe('Create a business rule', () => {
beforeEach(() => {
cy.restoreLocalStorage();
});
it('Navigate to business rules', () => {
cy.el('btnCompanySettings').click({ force: true });
cy.url().should('include', 'dm/settings/general');
cy.el('selectBreadcrumbs').click().find('.scrollable-content').children().contains('Bedrijfsregels').click();
});
});
describe('Create a business rule', () => {
beforeEach(() => {
cy.restoreLocalStorage();
});
it('Navigate to groups', () => {
cy.el('selectBreadcrumbs').click().find('.scrollable-content').children().contains('Groepen').click();
});
});
I don't want to define this for each describe though, is it possible to define this somewhere else so it runs implicitly?
I've tried before:spec in my plugin file:
on('before:spec', () => {
console.log('############ BEFORE SPEC ###############');
});
But that only runs before the spec, not before each test.
You can add the beforeEach() under the cypress/support/e2e.js This will run the same beforeEach before all your tests.

Cypress resets to cy.visit() after running each describe section

I have a spec file in Cypress below and every time it runs the spec, the "Analyze Section" succeeds but the "Other Section" fails due to it returning to the login page even though the before() hook at the root should just run once based on the level of nesting. I'm trying to make it so the login happens one time whenever any tests in this suite are run. Likewise, when any test in the "Analyze Section" are run we click the #HyperLinkAnalyze link one time to ensure we are on the proper page for any test. I was trying to make them cascade down but the beforeEach() call in each section ends up popping the page back out to the login page that happened in before().
context('Admin - Analyze Tab', { tags: ['#admin'] }, () => {
let user;
before(() => {
cy.visit(Cypress.env('admin_url'));
user = Cypress.env('admin_user');
cy.login(user.email, user.password);
});
describe('Analyze Section', ()=>{
beforeEach(() => {
cy.get('#HyperLinkAnalyze').click();
cy.get('#HyperLinkCampaignStats').click();
});
it('TEST 1', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
it('TEST 2', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
});
describe('Other Section', ()=>{
beforeEach(() => {
cy.get('#HyperLinkAnalyze').click();
cy.get('#HyperLinkXSellStats').click();
});
it('TEST 1', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
it('TEST 2', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
});
});
```js
You can try Cypress.session .
The new cy.session() command solves problem by caching and
restoring cookies, localStorage and sessionStorage after a successful
login. The steps that your login code takes to create the session will
only be performed once when it's called the first time in any given
spec file. Subsequent calls will restore the session from cache.
Set experimentalSessionSupport flag to true in the Cypress config or by using Cypress.config() at the top of a spec file.
Check below example -
const loginWithSession = () => {
cy.session(() => {
cy.visit(Cypress.env('admin_url'));
let user = Cypress.env('admin_user');
cy.login(user.email, user.password);// Can be parameterize
});
cy.visit(Cypress.env('admin_url'));//Or home page url
})
}
context('Admin - Analyze Tab', { tags: ['#admin'] }, () => {
before(() => {
loginWithSession();
});
describe('Analyze Section', ()=>{
beforeEach(() => {
loginWithSession();
cy.get('#HyperLinkAnalyze').click();
cy.get('#HyperLinkCampaignStats').click();
});
it('TEST 1', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
it('TEST 2', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
});
describe('Other Section', ()=>{
beforeEach(() => {
loginWithSession();
cy.get('#HyperLinkAnalyze').click();
cy.get('#HyperLinkXSellStats').click();
});
it('TEST 1', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
it('TEST 2', {}, () => {
cy.contains('#analytics-row1', 'Response Rate').should('be.visible');
});
});
});
In older version of Cypress you can use https://docs.cypress.io/api/cypress-api/cookies#Preserve-Once to preserve the cookies and Cypress will not clear it .
beforeEach(() => {
Cypress.Cookies.preserveOnce('session_id', 'remember_token')
})

Synchronous RxJs unsubscription not working

Demo: https://stackblitz.com/edit/rxjs-unsubscribe-issue?file=index.ts
Below code is not working
Error: Cannot read property 'unsubscribe' of undefined
const a = (): Observable<any> =>
new Observable(sub => {
sub.next(1);
return () => {
console.log('unsubscribe');
};
});
const observer = a().subscribe(
value => {
console.log('Subscription');
observer.unsubscribe();
},
e => console.log(e),
() => console.log('complete')
);
But the following code is working
const b = (): Observable<any> =>
new Observable(sub => {
setTimeout(()=>sub.next(1),0);
return () => {
console.log('unsubscribe');
};
});
const observer2 = b().subscribe(
value => {
console.log('Subscription b');
observer2.unsubscribe();
},
e => console.log(e),
() => console.log('complete')
);
Help me understand the reason behind it
as you mentioned in the title of your question, the first example is synchronous, so you get the first value while still inside of the .subscribe() method. Naturally, observer, which is supposed to have a Subscription object hasn't been initialized yet.
If you want to unsubscribe after receiving a single value I would suggest to use .take(1)

Setting options of a Kendo Grid that is inside an Editor Template

Inside the editor template of one of my Grids, A, I have another grid, B. I want to set read and update actions of this Grid based on the current Id in the A.
I've tried using Razor code inside the editor template like this:
// details grid: B
.Read("action", "controller", new { #Model.Id })
But Id was always null, probably because Razor is server-side.
So I've tried to set the Grid inside the main page, in the Edit event of the main Grid.
function EditHandler(e) {
if (e.model.isNew())
return;
var detailGrid = e.container.find("#details").data('kendoGrid');
detailGrid.dataSource.options.transport.read.url = "#Url.Action("", "")".concat('/', e.model.Id);
detailGrid.dataSource.options.transport.update.url = "#Url.Action("", "")".concat("/", e.model.Name);
}
This doesn't work neither. The definition of the Grid in the editor template will overwrite these properties.
This is the main Grid:
#(Html.Kendo().Grid<A>()
.Name("A")
.Columns(columns =>
{
columns.Bound(x => x.Id).Hidden();
columns.Bound(x => x.Name).HtmlAttributes(new { #style = "text-align: left" });
....
columns.Command(command =>
{
command.Edit(); command.Destroy();
});
})
.....
.Selectable()
.Navigatable()
.DataSource(ds => ds
.Ajax()
.Model(model =>
{
model.Id(x => x.Name);
})
.Read("", "")
.Update("", "")
.Destroy("", "")
.Create("", "")
)
.Events(e => e.Edit("EditHandler").Save("SaveHandler"))
)
Inside the editor template of this class, a.cshtml, I have another Grid that I want its Edit and Read Actions include the Id of the Grid A.
#(Html.Kendo().Grid<B>()
.Name("B")
.Columns(columns =>
{
columns.Bound(.....
})
.....
.Editable(edit => edit.Mode(GridEditMode.InCell))
.Selectable()
.Navigatable()
.DataSource(ds => ds
.Ajax()
.PageSize(5)
.Model(model =>
{
model.Id(x => x.Id);
})
.Read("", "")
.Update("", "")
).ToClientTemplate()
)
UPDATE
function EditHandler(e) {
if (e.model.isNew())
return;
var detailGrid = e.container.find("#details").data('kendoGrid');
detailGrid.dataSource.read({ Id: e.model.Id });
detailGrid.dataSource.update({ Name: e.model.Name });
}
As Dion noted, it needs 2 changes:
In the child Grid, B, set the auto bind to false: .AutoBind(false)
Set the ID with read method, not manually as I've been trying.
To overcome this case, I will trigger Grid-B's read manually inside Grid-A's edit event. Follow this setup:
Grid-B Config:
.AutoBind(false)
.DataSource(ds => ds.Ajax()
.Read("action", "controller")
.Update(url => url.Action("update", "controller").Data(paramData)))
Modify Grid-A edit function:
function EditHandler(e) {
var gridB = $("#gridB").getKendoGrid(),
model = e.model;
gridB.dataSource.read({Id: model.Id});
}
// get Id for update
function paramData() {
return { Id : $("#Id").val(); };
}
Hope this help.
Note: Unlike read, update will be triggered after popup shown then Id field will be already have its value. Therefor you can use Url.Action(...).Data() for this case.

Resources