React native - executing store method via action while retaining `this` context of component - action

Hopefully this code sample does a good job of explaining the problem that I am trying to solve.
/**
* We happen to be working in React Native here, so fluxible's
* connectToStores as well as the implementation of flux-react
* are non-starters here due to both's peer dependency on
* react.
*/
// Store.js
var Store = {
getState: function () {
var endpoint = '/endpoint';
var promise = fetch(endpoint)
.then(response => response.json());
return Promise.resolve(promise)
.then((data) => {
this.setState({
hasData: true,
data: data
});
})
.done();
}
};
module.exports = Store;
// Component.js
var Component = React.createClass({
getInitialState: function () {
return {
hasData: false,
data: {}
};
},
componentDidUpdate: function () {
console.log('this.state', this.state);
},
componentDidMount: function () {
/**
* Rather than directly apply here, possible to wrap the Store somehow
* so that the Component scope of `this` is applied automatically when
* calling _any_ of Store's methods?
*
* Even better, how to execute an action here that triggers a method in
* the Store and passes along `this` from the Component as the context.
*/
Store.getState.apply(this);
},
render: function () {
return (
<View></View>
);
}
});
module.exports = Component;

What you did with passing the 'this' should work. However, I would suggest doing something like:
this.setState(Store.getState())
If you are repeating this multiple times, you can use a mixin. In a mixin, your 'this' is the context.

Related

Invalid syntax on EmberJS Octane observers

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.

Im trying to mock a function from a service but Jest keeps calling the actual function instead of the mock function

I'm using Jest to test a function from a service that uses axios to make some api calls. The problem is that Jest keeps calling the actual services function instead of the mocked service function. Here is all of the code:
The tests:
// __tests__/NotificationService.spec.js
const mockService = require('../NotificationService').default;
beforeEach(() => {
jest.mock('../NotificationService');
});
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
expect.assertions(1);
const data = await mockService.fetchNotifications();
console.log(data);
expect(data).toHaveProperty('data.bell');
});
});
The mock:
// __mocks__/NotificationService.js
const notifData = {
bell: false,
rollups: [
{
id: 'hidden',
modifiedAt: 123,
read: true,
type: 'PLAYLIST_SUBSCRIBED',
visited: false,
muted: false,
count: 3,
user: {
id: 'hidden',
name: 'hidden'
},
reference: {
id: 'hidden',
title: 'hidden',
url: ''
}
}
],
system: [],
total: 1
};
export default function fetchNotifications(isResolved) {
return new Promise((resolve, reject) => {
process.nextTick(() =>
isResolved ? resolve(notifData) : reject({ error: 'It threw an error' })
);
});
}
The service:
import axios from 'axios';
// hardcoded user guid
export const userId = 'hidden';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
}
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried a couple of variations of using require instead of importing the NotificationService, but it gave some other cryptic errors...
I feel like I'm missing something simple.
Help me please :)
The problem is that Jest keeps calling the actual services function instead of the mocked service function.
babel-jest hoists jest.mock calls so that they run before everything else (even import calls), but the hoisting is local to the code block as described in issue 2582.
I feel like I'm missing something simple.
Move your jest.mock call outside the beforeEach and it will be hoisted to the top of your entire test so your mock is returned by require:
const mockService = require('../NotificationService').default; // mockService is your mock...
jest.mock('../NotificationService'); // ...because this runs first
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
...
});
});

Mocking RouterStateSnapshot in Jasmine testing

Although I have been writing Angular 2 for a while now, I am only just writing my first Jasmine tests and have run into a little difficulty. I am trying to test that the CanActivate method of service implementing CanActivate is behaving itself, and is returning true or false as expected.
My method looks like this:
canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> {
return this.store$
.map( ( store: StoreState ) => store.currentUser )
.first()
.map( ( user ) => {
if ( user.isAuthenticated ) {
return true;
}
// TODO: This needs refactoring. Need to provide RouterStateSnapshot in test,
// rather than ignoring it!
this.redirectUrl = state ? state.url : '';
this.injector.get( Router ).navigate( ['/login'] );
return false;
} );
}
An extract of my test looks like this:
service = TestBed.get( AuthGuardService );
it( 'should prevent navigation', () => {
service.canActivate(null, null).subscribe((res) => expect( res ).toBeTruthy() );
} );
How do I mock/stub/whatever the second parameter of my call to service.canActivate, rather than simply passing in null?
describe('AuthGuard', () => {
let mockSnapshot: RouterStateSnapshot;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// so we can get the Router injected
RouterTestingModule,
// other imports as needed
],
// usual config here
});
// create a jasmine spy object, of the required type
// toString is because we have to mock at least one method
mockSnapshot = createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
});
it('should prevent non-authenticated access',
async(inject([AuthGuard, AuthService, Router], (guard: AuthGuard, auth: AuthService, router: Router) => {
// ensure we're logged out
auth.logout();
// set the url on our mock snapshot
mockSnapshot.url = '/protected';
// so we can spy on what's been called on the router object navigate method
spyOn(router, 'navigate');
expect(guard.canActivate(null, mockSnapshot)).toBeFalsy();
// check that our guard re-directed the user to another url
expect(router.navigate).toHaveBeenCalled();
})));
});
})
Here is my solution which I used for unit testing of Custom Router State Serializer
custom-serializer.ts
import { RouterStateSerializer } from '#ngrx/router-store';
import { RouterStateSnapshot, Params } from '#angular/router';
/**
* The RouterStateSerializer takes the current RouterStateSnapshot
* and returns any pertinent information needed. The snapshot contains
* all information about the state of the router at the given point in time.
* The entire snapshot is complex and not always needed. In this case, you only
* need the URL and query parameters from the snapshot in the store. Other items could be
* returned such as route parameters and static route data.
*/
export interface RouterStateUrl {
url: string;
params: Params;
queryParams: Params;
}
export class CustomRouterStateSerializer
implements RouterStateSerializer<RouterStateUrl> {
serialize(routerState: RouterStateSnapshot): RouterStateUrl {
let route = routerState.root;
while (route.firstChild) {
route = route.firstChild;
}
const { url, root: { queryParams } } = routerState;
const { params } = route;
// Only return an object including the URL, params and query params
// instead of the entire snapshot
return { url, params, queryParams };
}
}
custom-serializer.spec.ts
import { CustomRouterStateSerializer } from './utils';
import { RouterStateSnapshot } from '#angular/router';
describe('Utils CustomRouterStateSerializer', () => {
let mockSnapshot: RouterStateSnapshot;
let serializer: CustomRouterStateSerializer;
let mockSnapshotProxy;
beforeEach(() => {
mockSnapshot = jasmine.createSpyObj<RouterStateSnapshot>('RouterStateSnapshot', ['toString']);
serializer = new CustomRouterStateSerializer();
});
it('should serialize RouterStateSnapshot to subset of params', () => {
mockSnapshotProxy = new Proxy(mockSnapshot, {
get(target, prop) {
if (prop === 'root') {
return {
params: {
id: 100
},
queryParams: {
name: 'John'
}
};
} else if (prop === 'url') {
return '/orders';
}
},
});
const result = serializer.serialize(mockSnapshotProxy);
expect(result.url).toBe('/orders');
expect(result.params.id).toBe(100);
expect(result.queryParams.name).toBe('John');
});
});
I used jasmine.createSpyObj to create object with proper type and Proxy to pass in required properties

Ember.run.debounce does not debounce

I am using Twitter Typeahead.js in a subcomponent in Ember which I feed a dataSource function (see below).
This dataSource function queries a remote server. This query I would like to have debounced in Ember which does not seem to work.
Does this have to do with the runloop? Anything I should wrap?
import Ember from 'ember';
export default Ember.Component.extend({
dataResponse: [],
dataSource: function () {
var component = this;
// function given to typeahead.js
return function (query, cb) {
var requestFunc = function () {
var encQuery = encodeURIComponent(query);
Ember.$.getJSON('/api/autocompletion?prefix=' + encQuery).then(function (result) {
// save results
component.set('dataResponse', result.autocompletion);
// map results
var mappedResult = Ember.$.map(result.autocompletion, function (item) {
return { value: item };
});
cb(mappedResult);
});
};
// this is not debounced, why? :|
Ember.run.debounce(this, requestFunc, 500); // debounce by 500ms
};
}.property()
});
Note: I do not use Bloodhound with Typeahead.js since I need access to the results. A custom solution seemed easier at first.
Debounce works by creating a unique key based on the context/function. When you call it subsequent times it compares the existing keys to the context/function key passed in. You are passing in a different function every time you call debounce, which is why it isn't working how you're expecting it to work.
Taking the advice from #Kingpin2k I refactored the code like this:
import Ember from 'ember';
export default Ember.Component.extend({
dataResponse: [],
dataSource: function () {
var component = this;
var queryString = null;
var callBack = null;
var requestFunc = function () {
var encQuery = encodeURIComponent(queryString);
Ember.$.getJSON('/api/autocompletion?prefix=' + encQuery).then(function (result) {
// save results
component.set('dataResponse', result.autocompletion);
var mappedResult = Ember.$.map(result.autocompletion, function (item) {
return { value: item };
});
callBack(mappedResult);
});
};
// function used for typeahead
return function (q, cb) {
queryString = q;
callBack = cb;
Ember.run.debounce(this, requestFunc, 500); // debounce by 500ms
};
}.property()
});

How to write unit test for AngularJS model

I've got a basic model that I am trying to write a simple unit test suite for, and I'm clearly missing something...
The code for the model looks like this:
angular.module('AboutModel', [])
.factory(
'AboutModel',
[
function () {
var paragraphs = [];
var AboutModel = {
setParagraphs: function (newParagraphs) {
paragraphs = newParagraphs;
},
getParagraphs: function () {
return paragraphs;
}
};
return AboutModel;
}
]
);
The requirement is simple: provide a getter and a setter method for the private array called paragraphs.
And here is as far as I have got with the test suite code:
describe('Testing AboutModel:', function () {
describe('paragraphs setter', function () {
beforeEach(module('AboutModel'));
it('sets correct value', inject(function (model) {
// STUCK HERE
// don't know how to access the model, or the setParagraphs() method
}));
});
describe('paragraphs getter', function () {
// not implemented yet
});
});
I've been doing quite a bit of google research on the web, but so far no joy.
The solution must be simple; please help!
And it might even be the case that there's a better way of implementing the Model... open to suggestions to make it better.
For anyone interested, the full source code is here:
https://github.com/mcalthrop/profiles/tree/imp/angular
thanks in advance
Matt
You need to run a beforeEach in your test to inject the model instance and then assign it to a variable which you can then re-use through out your tests.
var AboutModel;
beforeEach(inject(function (_AboutModel_) {
AboutModel = _AboutModel_;
}));
You can then access your getter like so:
AboutModel.getParagraphs();
I have tweaked your original model slightly as I feel it reads a little better (my preference):
'use strict';
angular.module('anExampleApp')
.factory('AboutModel', function () {
var _paragraphs;
// Public API here
return {
setParagraphs: function (newParagraphs) {
_paragraphs = newParagraphs;
},
getParagraphs: function () {
return _paragraphs;
}
};
});
And then for testing I would use a combination of the standard Jasmine tests and spies:
'use strict';
describe('Service: AboutModel', function () {
beforeEach(module('anExampleApp'));
var AboutModel, paragraphs = ['foo', 'bar'];
beforeEach(inject(function (_AboutModel_) {
AboutModel = _AboutModel_;
}));
it('should set new paragraphs array', function () {
AboutModel.setParagraphs([]);
expect(AboutModel.getParagraphs()).toBeDefined();
});
it('should call setter for paragraphs', function () {
spyOn(AboutModel, 'setParagraphs');
AboutModel.setParagraphs(paragraphs);
expect(AboutModel.setParagraphs).toHaveBeenCalledWith(paragraphs);
});
it('should get 2 new paragraphs', function () {
AboutModel.setParagraphs(['foo', 'bar']);
expect(AboutModel.getParagraphs().length).toEqual(2);
});
});