Function calls are not supported in decorators but 'Animations' was called - angular-cli-v7

I have an Animation class given below:
import { trigger, state, style, transition, animate } from '#angular/animations';
export class Animations {
constructor() {}
animate = animate('.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)');
side() {
return trigger(`visibilityAnimation`, [
state('false', style({
transform: '{{ side }}',
display: 'none'
}), { params: { side: 'translateX(100%)' } }),
state('true', style({
transform: 'translateX(0)',
display: 'block'
})),
transition('false <=> true', this.animate),
]);
}
top() {.....}
chooseAnimation() {....}
background() {....}
}
In one of my components I'm using as follows:
import { Animations } from './animations';
const animations = new Animations();
#Component({
selector: 'app-nav-user',
templateUrl: './nav-user.component.html',
styleUrls: ['./nav-user.component.scss'],
animations: [
animations.chooseAnimation(),
animations.background()
]
})
When I use ng build --prod --aot --output-hashing=all, I get the above error.
Note: I'm using angular cli v7.

I had a similar situation happen to me while trying to code parameterized animations. Writing a function that returns the animation object is the intuitive thing to do, and after the error you would think storing the return in a constant and passing that to the decorator would work, but it doesn't with AOT. The reason has to do with the ordem of the compilation, so to speak. The AOT compiler will resolve metadata first, and it wont deal with function calls at all, so even if you try to resolve it outside of the decorator, it's all the same.
So what you should do is export the trigger(...) object as a constant and use the animation option params to do all necessary configurations, like you did with the side parameter in your example.
I can't really help you with much more, as you didn't share the part of the code actually triggering the error, the chooseAnimation method, but you should be able to get the idea behind it, as you already know how to use the options.

I had the same problem, so I'm just expanding on the answer by #Henrique Erzinger, which helped me solve it.
All you need to do is make sure there are no user-defined parameters in an animation function - in other words, all the parameters are (so to say) hardcoded.
Your function fadeIn, for example, can be called from the decorator by using animations: [fadeIn()] in the decorator, but the function definition itself cannot take any parameters.
// this WILL work
export function fadeIn(): AnimationTriggerMetadata {
return trigger('fadeIn', [
transition(':enter', [
// only static code, no variables allowed ...
])
]);
}
// this will NOT work
export function fadeIn(time = 300): AnimationTriggerMetadata {
return trigger('fadeIn', [
transition(':enter', [
group([
animate(time + 'ms' .... // NOT allowed
])
]);
}

Related

Spartacus Storefront Multisite I18n with Backend

We've run into some problems for our MultiSite Spartacus setup when doing I18n.
We'd like to have different translations for each site, so we put these on an API that can give back the messages dependent on the baseSite, eg: backend.org/baseSiteX/messages?group=common
But the Spartacus setup doesn't let us pass the baseSite? We can
pass {{lng}} and {{ns}}, but no baseSite.
See https://sap.github.io/spartacus-docs/i18n/#lazy-loading
We'd could do it by overriding i18nextInit, but I'm unsure how to achieve this.
In the documentation, it says you can use crossOrigin: true in the config, but that does not seem to work. The type-checking say it's unsupported, and it still shows uw CORS-issues
Does someone have ideas for these problems?
Currently only language {{lng}} and chunk name {{ns}} are supported as dynamic params in the i18n.backend.loadPath config.
To achieve your goal, you can implement a custom Spartacus CONFIG_INITIALIZER to will populate your i18n.backend.loadPath config based on the value from the BaseSiteService.getActive():
#Injectable({ providedIn: 'root' })
export class I18nBackendPathConfigInitializer implements ConfigInitializer {
readonly scopes = ['i18n.backend.loadPath']; // declare config key that you will resolve
readonly configFactory = () => this.resolveConfig().toPromise();
constructor(protected baseSiteService: BaseSiteService) {}
protected resolveConfig(): Observable<I18nConfig> {
return this.baseSiteService.getActive().pipe(
take(1),
map((baseSite) => ({
i18n: {
backend: {
// initialize your i18n backend path using the basesite value:
loadPath: `https://backend.org/${baseSite}/messages?lang={{lng}}&group={{ns}}`,
},
},
}))
);
}
}
and provide it in your module (i.e. in app.module):
#NgModule({
providers: [
{
provide: CONFIG_INITIALIZER,
useExisting: I18nBackendPathConfigInitializer,
multi: true,
},
],
/* ... */
})
Note: the above solution assumes the active basesite is set only once, on app start (which is the case in Spartacus by default).

Angular in-memory-web-api method always returns 404 NotFound in the brower's console even if the tests passed

I'm new to unit testing in Angular (using Jasmine and Karma)
I'm trying to create some tests for my httpService, apparently the tests are OK.
But sometimes when I either run ng test, or refresh the browser, I found that one of the test in one of the 3 test suites has failed with this message : Uncaught [object Object] thrown.
Another annoying thing is that no matter whether all of the tests pass or any of them fail, if you check the browser's console, you'll ALWAYS find this message :
I'm attaching the code in a zip file (uploaded to Drive). You only need to run npm install and npm start.
I really hope you can help me understand why this testing behaves like a Russian roulette.
The issue is calculator.component.spec.ts. You are not mocking loanService where it is going out and making HTTP calls. You should always mock external services.
Change calculator.component.spec.ts to:
import { NO_ERRORS_SCHEMA } from '#angular/core';
import { FormBuilder } from '#angular/forms';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { CalculatorComponent } from './calculator.component';
import { LoanService } from '../loan.service';
import { Campaign } from '../campaign';
import { of } from 'rxjs/internal/observable/of';
describe('CalculatorComponent', () => {
let component: CalculatorComponent;
let fixture: ComponentFixture<CalculatorComponent>;
let mockLoanService: any;
beforeEach(async(() => {
// mockLoanService object, first parameter ('loanService') is optional, second paramter => array of methods needing
// mock for component
mockLoanService = jasmine.createSpyObj('loanService', ['getCurrentCampaign', 'getMonthlyAmount']);
TestBed.configureTestingModule({
declarations: [ CalculatorComponent ],
imports: [],
// NO_ERRORS_SCHEMA to ignore child components, if you need the
// painting of the DOM of the child components/directives, put them in declarations
schemas: [NO_ERRORS_SCHEMA],
providers: [
FormBuilder,
// provide the mock for LoanService
{ provide: LoanService, useValue: mockLoanService },
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CalculatorComponent);
component = fixture.componentInstance;
// getCurrentCampaig is related to ngOnInit so we have to mock it
mockLoanService.getCurrentCampaign.and.returnValue(of({
id: 1,
campaign_name: 'Donald Trump 2020',
min_quota: -200000000,
max_quota: 0,
max_amount: 0,
min_amount: 0,
tea: 1,
payment_date: new Date(),
currency: 'Fake News',
} as Campaign))
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I have written some comments in the file itself. By the way, Donald Trump 2020 and Fake News are just jokes, I have no political affiliation but I like writing jokes in my unit tests for other developers :).
Some notes:
1.) Whenever you are injecting a service, always mock it. You are testing the component and component alone, you have to assume that the service will do its job because it is already being tested.
2.) Check out NO_ERRORS_SCHEMA. It basically ignores all components/directives in your HTML that is not in the declarations array. If you are writing a test where you click the button of a child component and it affects this component, then declare it in declarations (basically if you need the actual implementation of the child component, declare it). Otherwise, use NO_ERRORS_SCHEMA.
3.) Importing SharedModule in all unit tests is not good in my opinion. It will make your unit tests slow. Instead, take advantage of declarations and providers and give the component what it needs and JUST what it needs (not extra stuff).
4.) A really good class in PluralSight called Unit Testing in Angular.
Taking that class, you will have a better understanding of Unit/Integration testing. Maybe buy a subscription to PluralSight or start a free trial.

What is the proper way to unit test Service with NestJS/Elastic

Im trying to unit test a Service that uses elastic search. I want to make sure I am using the right techniques.
I am new user to many areas of this problem, so most of my attempts have been from reading other problems similar to this and trying out the ones that make sense in my use case. I believe I am missing a field within the createTestingModule. Also sometimes I see providers: [Service] and others components: [Service].
const module: TestingModule = await Test.createTestingModule({
providers: [PoolJobService],
}).compile()
This is the current error I have:
Nest can't resolve dependencies of the PoolJobService (?).
Please make sure that the argument at index [0]
is available in the _RootTestModule context.
Here is my code:
PoolJobService
import { Injectable } from '#nestjs/common'
import { ElasticSearchService } from '../ElasticSearch/ElasticSearchService'
#Injectable()
export class PoolJobService {
constructor(private readonly esService: ElasticSearchService) {}
async getPoolJobs() {
return this.esService.getElasticSearchData('pool/job')
}
}
PoolJobService.spec.ts
import { Test, TestingModule } from '#nestjs/testing'
import { PoolJobService } from './PoolJobService'
describe('PoolJobService', () => {
let poolJobService: PoolJobService
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PoolJobService],
}).compile()
poolJobService = module.get<PoolJobService>(PoolJobService)
})
it('should be defined', () => {
expect(poolJobService).toBeDefined()
})
I could also use some insight on this, but haven't been able to properly test this because of the current issue
it('should return all PoolJobs', async () => {
jest
.spyOn(poolJobService, 'getPoolJobs')
.mockImplementation(() => Promise.resolve([]))
expect(await poolJobService.getPoolJobs()).resolves.toEqual([])
})
})
First off, you're correct about using providers. Components is an Angular specific thing that does not exist in Nest. The closest thing we have are controllers.
What you should be doing for a unit test is testing what the return of a single function is without digging deeper into the code base itself. In the example you've provided you would want to mock out your ElasticSearchServices with a jest.mock and assert the return of the PoolJobService method.
Nest provides a very nice way for us to do this with Test.createTestingModule as you've already pointed out. Your solution would look similar to the following:
PoolJobService.spec.ts
import { Test, TestingModule } from '#nestjs/testing'
import { PoolJobService } from './PoolJobService'
import { ElasticSearchService } from '../ElasticSearch/ElasticSearchService'
describe('PoolJobService', () => {
let poolJobService: PoolJobService
let elasticService: ElasticSearchService // this line is optional, but I find it useful when overriding mocking functionality
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PoolJobService,
{
provide: ElasticSearchService,
useValue: {
getElasticSearchData: jest.fn()
}
}
],
}).compile()
poolJobService = module.get<PoolJobService>(PoolJobService)
elasticService = module.get<ElasticSearchService>(ElasticSearchService)
})
it('should be defined', () => {
expect(poolJobService).toBeDefined()
})
it('should give the expected return', async () => {
elasticService.getElasticSearchData = jest.fn().mockReturnValue({data: 'your object here'})
const poolJobs = await poolJobService.getPoolJobs()
expect(poolJobs).toEqual({data: 'your object here'})
})
You could achieve the same functionality with a jest.spy instead of a mock, but that is up to you on how you want to implement the functionality.
As a basic rule, whatever is in your constructor, you will need to mock it, and as long as you mock it, whatever is in the mocked object's constructor can be ignored. Happy testing!
EDIT 6/27/2019
About why we mock ElasticSearchService: A unit test is designed to test a specific segment of code and not make interactions with code outside of the tested function. In this case, we are testing the function getPoolJobs of the PoolJobService class. This means that we don't really need to go all out and connect to a database or external server as this could make our tests slow/prone to breaking if the server is down/modify data we don't want to modify. Instead, we mock out the external dependencies (ElasticSearchService) to return a value that we can control (in theory this will look very similar to real data, but for the context of this question I made it a string). Then we test that getPoolJobs returns the value that ElasticSearchService's getElasticSearchData function returns, as that is the functionality of this function.
This seems rather trivial in this case and may not seem useful, but when there starts to be business logic after the external call then it becomes clear why we would want to mock. Say that we have some sort of data transformation to make the string uppercase before we return from the getPoolJobs method
export class PoolJobService {
constructor(private readonly elasticSearchService: ElasticSearchService) {}
getPoolJobs(data: any): string {
const returnData = this.elasticSearchService.getElasticSearchData(data);
return returnData.toUpperCase();
}
}
From here in the test we can tell getElasticSearchData what to return and easily assert that getPoolJobs does it's necessary logic (asserting that the string really is upperCased) without worrying about the logic inside getElasticSearchData or about making any network calls. For a function that does nothing but return another functions output, it does feel a little bit like cheating on your tests, but in reality you aren't. You're following the testing patterns used by most others in the community.
When you move on to integration and e2e tests, then you'll want to have your external callouts and make sure that your search query is returning what you expect, but that is outside the scope of unit testing.

Says Jasmine spy is not being called, but I can see that its being called

I can't figure out why Jasmine is claiming that the function I'm spying on isn't being called, especially since it is logging in buildLinksObj when called through and not calling when I remove .and.callThrough() I feel like I've written similar code a bunch of times before without any problem. I'm using Jasmine 2.9
The error message I'm getting is:
1) addToLinks should call buildLinksObj if its given an object with children
it should add the personalized links to PageApp.meta.analytics.links
Expected spy buildLinksObj to have been called.
at UserContext.<anonymous> (http://localhost:9877webpack:///tests/specs/common/FetchPersonalContent.spec.js:854:0 <- tests/app-mcom.js:104553:48)
Here's the except of my code:
FetchPersonalContent.js
const buildLinksObj = (responseObj = {}, targetObj, PageApp) => {
console.log('it logs in buildLinksObj') // This is logging!
}
const addToLinks = (responseArr, personalizedLinks) => {
responseArr.forEach((media) => {
const type = media.type;
const typeObj = media[type];
buildLinksObj(typeObj, personalizedLinks, PageApp);
if (typeObj && typeObj.children) {
console.log('has children!')
console.log('typeObj.children is: ', typeObj.children);
typeObj.children.forEach((child) => {
console.log('has a child')
buildLinksObj(child, personalizedLinks, PageApp);
console.log('buildLinksObj was definitely called. what the heck?')
});
}
});
}
export {buildLinksObj, addToLinks, FetchPersonalContent as default,
};
FetchPersonalContent.spec.js
import * as FetchPersonalContent from '../../../src/FetchPersonalContent'; // my path is definitely correct
describe('it should add the personalized links to PageApp.meta.analytics.links', () => {
it('addToLinks should call buildLinksObj if its given an object with children ', () => {
spyOn(FetchPersonalContent, 'buildLinksObj').and.callThrough();
FetchPersonalContent.addToLinks([{
"personalId": 30718,
"type": "carousel",
"carousel": {}
}], {});
expect(FetchPersonalContent.buildLinksObj).toHaveBeenCalled();
});
});
I'd really appreciate any help!
I have a feeling FetchPersonalContent.buildLinksObj in the spec file is not pointing to the same instance as buildLinksObj in the FetchPersonalContent.js file.
Why is export {FetchPersonalContent as default} required? I am assuming you have shared the complete content of FetchPersonalContent.js in your question.
Possible solutions:
You can try removing FetchPersonalContent from the export statement.
Or
Instead of
export {buildLinksObj, addToLinks, FetchPersonalContent as default,
};
You can directly export the constants in FetchPersonalContent.js file.

Angular2 trigger animation from parent component

I'm trying to trigger an animation on an hidden element of a child component. To be simple, the animation should occur when the element appears, and then each time a user click on a button from the parent component.
Here is simple code :
(tried to plunkr it, but impossible to import trigger component from angular core)
app.ts
import {ChildComponent} from './child';
#Component({
selector: 'my-app',
template: `
<button id="showChildButton" (click)="setShowChild()">Show Child</button>
<button id="triggerAnimation">Trigger animation</button>
<child-component *ngIf="showChild"></child-component>
`
.....
})
export class App {
showChild: boolean = false;
setShowChild() {
this.showChild = true;
}
}
child.ts
import {
Component,
trigger,
state,
style,
transition,
animate
} from '#angular/core'
#Component({
selector: 'child-component',
template: `<h1 [#inflateIn]>Hello</h1>`,
animations: [
trigger('inflateIn', [
transition('void => *', [
animate(100, style({ transform: 'scale(1.1)'}))
]),
transition('* => *', [
animate(100, style({ transform: 'scale(1.1)'}))
])
])
]
})
export class ChildComponent {
}
I am able to animate the , the first time it appears, but I can't figure out how to trigger this animation again, when clicking on button #triggerAnimation of the parent component.
I searched for examples but I didn't find anything that solve my case.
Thanks for your help
You have to toggle the showChild variable. You can change your setShowChild() method as follows
setShowChild() {
this.showChild === false ? true : false;
}
It checks if this.showChild is false so make it true otherwise false to hide it again.
I hope this is what you wanted to get the desired result?

Resources