Invalid syntax on EmberJS Octane observers - ember.js

I'm trying to use Ember observers with EmberJS Octane latest version (4.1.0), but it does not seem to work.
Here is what I'm trying to achieve :
export default class SummaryService extends Service {
#service store;
#service authentication;
#reads('authentication.userId') userId;
#tracked weekPosts;
#tracked monthPosts;
#observer('userId')
loadPosts() {
this._loadPosts(this.userId);
}
_loadPosts(userId) {
this.store.query('posts', { filter: { 'current-week': 1, 'user-id': userId } })
.then((posts) => {
this.set('weekPosts', posts);
});
this.store.query('posts', { filter: { 'current-month': 1, 'user-id': userId } })
.then((posts) => {
this.set('monthPosts', posts);
});
}
}
=> The syntax is invalid.
I also tried :
#observer('userId', function() {
this._loadPosts();
});
=> The observer is indeed called, but this is undefined.
I also tried :
init() {
super.init(...arguments);
this.addObserver('currentUserId', this, '_loadPosts');
}
=> But this one does not call any method (even with inline method definition).
Finally, my last attempt was to use #computed properties for weekPosts and monthPosts instead, like this :
export default class SummaryService extends Service {
/* ... */
#computed('userId')
get weekPosts() {
return this.store.query('posts', { filter: { 'current-week': 1 } })
.then((posts) => { return posts; });
}
}
=> But it always returns a Promise, so I can't call .reduce on it from a computed property used by a Component :
export default class SummaryComponent extends Component {
#computed('weekPosts')
get weekPostsViewsCount() {
return this.weekPosts.reduce((sum, post) => { return sum + post.viewCount });
}
}
I finally got something working pretty ugly using an ArrayProxy.extend(PromiseProxyMixin) returned by the weekPosts computed property, but I'm definitely not happy with this for the following reasons :
So much code for such a simple thing
Everything (component, template) which uses the weekPosts has to make sure the promise is fulfilled before working with it
The promise is an implementation detail of the service and should not be visible in any way out of it
Thanks !

Observers won't work for what you want to do -- since it looks like you want to reactively re-fetch data (using ember-data) based on when userId changes, I have a library suggestion:
https://github.com/NullVoxPopuli/ember-data-resources
With this library, we can replace most of your service with this:
import { query } from 'ember-data-resources';
export default class SummaryService extends Service {
#service authentication;
#reads('authentication.userId') userId;
_weekPosts = query(this, 'posts', () => ({
filter: { 'current-week': 1, 'user-id': this.userId
}));
_monthPosts = query(this, 'posts', () => ({
filter: { 'current-month': 1, 'user-id': this.userId
}));
get weekPosts() {
return this._weekPosts.records ?? [];
}
get monthPosts() {
return this._monthPosts.records ?? [];
}
get isLoading() {
return this._weekPosts.isLoading || this._monthPosts.isLoading;
}
}
The advantage here is that you also have the ability to manage error/loading/etc states.
This uses a technique / pattern called "Derived state", where instead of performing actions, or reacting to changes, or interacting withe lifecycles, you instead define how data is derived from other data.
In this case, we have known data, the userId, and we want to derive queries, using query from ember-data-resources, also uses derived state to provide the following api:
this._weekPosts
.records
.error
.isLoading
.isSuccess
.isError
.hasRun
Which then allows you to define other getters which derive data, weekPosts, isLoading, etc.
Derived state is much easier to debug than observer code -- and it's lazy, so if you don't access data/getters/etc, that data is not calculated.

Related

Unit testing nestjs guards Unknown authentication strategy

Trying to write unit tests as described here but no idea how to get around this error
Exception has occurred: Error: Unknown authentication strategy "test-jwt"
at attempt (/home/user/Workspace/project/node_modules/passport/lib/middleware/authenticate.js:190:39)
at authenticate
auth file
import { Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
#Injectable()
export class MyGuard extends AuthGuard('test-jwt') { }
test
import { ExecutionContext } from "#nestjs/common";
import { MyGuard } from "./mygaurd";
it('test' () => {
const context: ExecutionContext = {
switchToHttp: () => context,
getRequest: () => {
return {
headers: {
authorization: `bearer ${jwt}`
}
}
},
getResponse: () => { }
} as unknown as ExecutionContext
const guard = new MyGuard()
expect(guard.canActivate(context)).toBeTrue();
})
The actual implementation works fine, I add it to a controller.
#UseGuards(MyGuard)
export class MyController {
I don't even need to add it as a provider or anything in my setup so not sure what other code to include.
I implemented a custom strategy which may be related
import { Strategy, ExtractJwt } from "passport-jwt";
import { Injectable } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
#Injectable()
export class MyStrategy extends PassportStrategy(Strategy, 'test-jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secret'
})
}
async validate(payload) {
...
}
}
And of course MyStrategy is added as a provider in my app.
I have unit tests covering my custom strategy already so it is really just the guard left
EDIT:
Trying Jay's suggestion below gets me a little closer but i'm still struggling.
It seems passport.use() expects a name and Strategy rather than a function (so TS compilation fails) so I tried
import passport, { Strategy } from "passport";
...
passport.use('test-jwt', {
authenticate: (payload) => true
} as Strategy);
and the error disappears, but the test now outputs
expect(received).toBeTrue()
Expected value to be true:
true
Received:
{}
Any further suggestions?
This is one of those quirky things with passport that I never thought I'd see. So, passport uses strategy names to determine what authentication method is actually being used, right? All of these strategies get registered to the passport context using passport.use(name, method) in the general scheme of things. In the context of Nest, this happens when you create a custom strategy, extend PassportStrategy and add the strategy as a provider as seen here. Later, the method passport.authenticate(strategy, (err, req, res, next) is called during the AuthGuard#canActivate method (the codes a bit complex, but this is where it happens). Because passport has never seen passport.use('test-jwt', authMethod) in the context of your test, it ends up not knowing what to do other than throwing the error about "Unknown authentication strategy".
Normally, the validate method is what becomes the authMethod, but if you're just needing this for the context of your test you can do something like
it('test' () => {
passport.use('test-jwt', (payload) => true);
const context: ExecutionContext = {
switchToHttp: () => context,
getRequest: () => {
return {
headers: {
authorization: `bearer ${jwt}`
}
}
},
getResponse: () => { }
} as unknown as ExecutionContext
const guard = new MyGuard()
expect(guard.canActivate(context)).toBeTrue();
})
and it should work out all right. You an then modify the value returned from that method, or make it a jest.fn() so that you can check what it was called with and modify what it returns if you need to do extra testing on the guard.

How can I test computed properties in VueJS?

I'm using VueJS from Vue CLI. So all my components are in .vue format.
In one of my components, I have an array called fields in the data section.
//Component.vue
data() {
return {
fields : [{"name" : "foo", "title" : "Foosteria"}, {"name" : "bar", "title" : "Barrista"}]
}
}
I have a computed property that is a subset of fields
//Component.vue
computed : {
subsetOfFields () {
// Something else in component data determines this list
}
}
I've set up all of my unit tests in jasmine like this and they work fine.
//Component.spec.js
import Vue from 'vue'
import MyComponent from 'Component.vue'
describe("Component test", function() {
var myComponentVar = new Vue(MyComponent);
var vm = myComponentVar.$mount();
beforeEach(function() {
vm = myComponentVar.$mount();
);
afterEach(function() {
vm = myComponentVar.$destroy();
});
it("First spec tests something", function() {
...
});
});
For everything else, doing something inside the spec, then running assertions on the data objects works just fine. However, running an assertion on subsetOfFields always returns an empty array. Why so? What should I do, in order to be able to test it?
FYI, I even tried nesting the spec inside another describe block and then adding a beforeEach which initializes the fields array. It did not work.
However, initializing fields inside the generic beforeEach function worked. But I don't want to initialize the fields array with that mock data for the other specs.
I came across this link that talks about testing and the section you'll need to look at is the Vue.nextTick(...) section
https://alligator.io/vuejs/unit-testing-karma-mocha/
The block I'm talking about is below:
import Vue from 'vue';
// The path is relative to the project root.
import TestMe2 from 'src/components/TestMe2';
describe('TestMe2.vue', () => {
...
it(`should update when dataText is changed.`, done => {
const Constructor = Vue.extend(TestMe2);
const comp = new Constructor().$mount();
comp.dataProp = 'New Text';
Vue.nextTick(() => {
expect(comp.$el.textContent)
.to.equal('New Text');
// Since we're doing this asynchronously, we need to call done() to tell Mocha that we've finished the test.
done();
});
});
});

Get an instance of TypeScript class?

I have some legacy code I want to start unit-testing. It's a class like this:
export class Controller {
private something: any;
constructor() { this.something = true; }
public getSomething(): any { return this.something; }
}
Trying to unit-test it with Mocha like this:
import Controller from '../../src/Controller';
describe('Controller', () => {
let subject: any;
beforeEach( () => {
subject = new Controller(); // compiler complains here
});
describe('getOptions()', () => {
it('should get something', () => {
let result: any = subject.getOptions();
if (typeof result !== 'object') {
throw new Error('Expected object but got ' + result);
}
});
});
});
Compiler complains:
[ts] Cannot use 'new' with an expression whose type lacks a call or
construct signature.
How do I get an instance of the Controller class to run tests against?
It doesn't work because you are trying to import the Controller as a default export while it isn't.
Try:
import {Controller} from '../../src/Controller';
or:
export default class Controller {
You can read more about imports in general on the MDN page here.

How to unit test an angular 2 component when I don't care about the dependencies? I just want to test some inner functions

I'm new to writing unit tests, and unfortunately I've already built a few "complicated" (for me) components which I am having a hard time even beginning to write tests for.
Here's a snippet of my code, including the constructor. Basically, I don't really care right now about these dependencies, I want to test some inner functions such as resizing based on array size, etc. For these, I can just create an Array.fill and should be good to go.
export class GalleryComponent implements OnInit {
photos = [];
galleryState: Observable<any>;
constructor(
private store: Store<any>,
private photoActions: PhotoActions,
private router: Router
) {
this.galleryState = this.store.select<any>(state => state.photos.gallery);
}
}
In my other components which have nothing in the constructor, instantiating the component in my test is as simple as new SomeComponent().
However, in the GalleryComponent above, I am wondering if there is a way that I can literally ignore the dependencies completely (for now), and instead just instantiate the component in a way that I can test some inner functions easily. For example, say I had the following function inside GalleryComponent:
function timesByTwo(number) {
return number * 2;
}
This is not at all related to any of the dependencies, so how can I just test that one function given that this component has 3 dependencies?
Thanks
If you truly don't care about testing anything at all that is associated with your dependencies, then in your spec you can just construct your component with null values for those dependencies.
import { AppComponent } from './app.component';
describe('App: Test', () => {
let component: AppComponent;
beforeEach(() => {
component = new AppComponent(null, null, null);
});
it('should create the app', () => {
expect(component).toBeTruthy();
});
it(`Should return 4`, () => {
expect(component.timesByTwo(2)).toEqual(4);
});
}
To get around your current usage of this.store.select in your constructor you can modify your constructor like so
constructor(
private store: Store<any>,
private photoActions: PhotoActions,
private router: Router
) {
if(this.store == null){
this.galleryState = null;
}else{
this.galleryState = this.store.select<any>(state => state.photos.gallery);
}
}
Otherwise you can mock your Store component in your test page. An example
import { Observable } from 'rxjs/Rx'
export class MockService extends EventService{
constructor() { super(null); }
getEvents(user:string){
return Observable.of([{val: "test"}]);
}
}
and then modify my code from above to be
let component: AppComponent;
let mockService: MockService;
beforeEach(() => {
mockService = new MockService()
component = new AppComponent(null, mockService, null);
});

Unit Testing Ember Services that Fetch Data

I have an ember service thats primary concern is to fetch data for a specific model and the descendants of the model. The reason I am using this in a service is because the route for this particular type is using a slug which is not the primary key and therefore needs to do a store.query instead of store.find. When we fetch this model I have some logic that peeks the ember store to see if we can load it from there before going to the api query. Also this vendor is watching for the slug change and updating the current model based on that.
The problem I am having is that this seems to have very little documentation when it comes to how to test a thing like this. In fact I don't see a section on testing services anywhere in the guides here http://guides.emberjs.com/v2.1.0/
This is a snippet of the service in question.
import Ember from 'ember';
export default Ember.Service.extend({
_vendorSlug: null,
vendor: null,
vendorSlug: function (key, value) {
if (arguments.length > 1) {
if (this._vendorSlug) {
return this._vendorSlug;
}
this._vendorSlug = value;
}
return this._vendorSlug;
}.property(),
ensureVendorLoaded: function (slug) {
var service = this,
vendorSlug = slug || service.get('vendorSlug'),
currentVendor = service.get('vendor'),
storedVendor;
if (!Ember.isNone(currentVendor) && (vendorSlug === currentVendor.get('slug'))) {
return new Ember.RSVP.Promise((resolve) => {
resolve(currentVendor);
});
} else {
var storedVendors = service.store.peekAll('vendor').filter((vendor) => {
return vendor.get('slug') === vendorSlug;
});
if (storedVendors.length) {
storedVendor = storedVendors[0];
}
}
if (!Ember.isNone(storedVendor)) {
service.set('vendorSlug', storedVendor.get('slug'));
return new Ember.RSVP.Promise((resolve) => {
resolve(storedVendor);
});
}
return service.store.queryRecord('vendor', {slug: vendorSlug}).then((vendor) => {
service.set('vendor', vendor);
service.set('vendorSlug', vendor.get('slug'));
return vendor;
});
},
_vendorSlugChanged: function () {
if (this.get("vendorSlug") === this.get("vendor.slug")) {
return;
}
this.ensureVendorLoaded();
}.observes('vendorSlug')
});
I would like to be able to assert a couple of scenarios here with the store interaction. Vendor already set, vendor loaded from the peek filter, and vendor loaded from query.
I think I have finally come to a reasonable conclusion. Let me share with you what I think may be the best way to approach unit testing services that rely on the store.
The answer really lies in the assumption we must make when writing unit tests. That is, everything outside of our logical unit should be considered to work properly and our units should be completely independent.
Thus, with a service relying on the store it is best to create a stub or mock (see this question to understand the difference between a mock and a stub) for the store. A stub for the store itself is quite simple. Something like this will do:
store: {
find: function() {
var mockedModel = Ember.Object.create({/*empty*/});
return mockedModel;
},
query: ...
}
If you prefer to use a mock instead you could do something like the following (i made this really fast so it might not work completely but its enough to get the idea across):
import Ember from 'ember';
class MockStore {
constructor() {
this.models = Ember.A([]);
}
createRecord(modelName, record) {
// add a save method to the record
record.save = () => {
return new Ember.RSVP.Promise((resolve) => {
resolve(true);
});
};
if (!this.models[modelName]) {
this.models[modelName] = Ember.A([]);
}
this.models[modelName].pushObject(record);
return record;
}
query(modelName, query) {
let self = this;
return new Ember.RSVP.Promise((resolve) => {
let model = self.models[modelName];
// find the models that match the query
let results = model.filter((item) => {
let result = true;
for (let prop in query) {
if (query.hasOwnProperty(prop)) {
if (!item[prop]) {
result = false;
}
else if (query[prop] !== item[prop]) {
result = false;
}
}
}
return result;
});
resolve(results);
});
}
}
export default MockStore;
Next all you have to do is to set the store property (or whatever your calling it) on your service to a new mock store instance when you run a test. I did this like so:
import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';
import MockStore from '../../helpers/mock-store';
let session;
let store;
const username = '';
const password = '';
moduleFor('service:authentication', 'Unit | Service | authentication', {
beforeEach() {
session = Ember.Object.create({});
store = new MockStore();
}
});
test('it should authenticate the user', function (assert) {
// this sets the store property of the service to the mock store
let authService = this.subject({session: session, store: store});
authService.authenticate(username, password).then(() => {
assert.ok(session.get('username'));
});
});
The documentation on testing these situations is definitely poor, so perhaps there is a better method, but this is what I will be rolling with for now. Also, if you check out the Discourse project, which uses ember, they follow a similar pattern to what I described here, but in a little more advanced manner.
I'm not sure this is the answer you want, but I'll give it a shot anyway. An Ember Service is not really much more than an Ember Object and if you're "unit testing" that Service, it should be in isolation of its dependencies (otherwise it wouldn't be a unit test).
From my understanding (and this could be wrong). If you want to test that service you need to replace the store with a mock implementation.
//tests/unit/services/my-service.js
test('some scenario', function(assert) {
let service = this.subject({
store: Ember.Object.create({
peekAll(modelName){
//Return array for this scenario
},
query(model, params){
//Return array for this scenario
}
});
});
assert.ok(service);
});
I also think this is why there's little documentation testing services.
One resource I recommend about services is this talk from the Chicago Ember Meetup