AngularJS - testing factories which store state/data - jasmine

I am working on new version of an app using legacy API (I have no controll of what the API returns etc.. ).
On the app init I request & store some site-wide info the factory which I have called stateFactory. Inside the stateFactory there is categories property (array of objects) which is storing the id -> category name relation.
Inside my app template I am using a filter to extract the name of the category by id {{ cat_id | categoryNameByIdFilter }} which is doing a lookup in the stateFactory.categories and returns the category name.
How do I write a unit test for such functionality (jasmine, mocha, chai, anything)?
// represantion of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function(){
return {
categories = [
{ cat_id: 1, cat_name: "some category name" },
{ cat_id: 2, cat_name: "another category name" }
];
};
}])
// categoryNameByIdFilter
app.factory('categoryNameByIdFilter', ['stateFactory', function(stateFactiry){
return function(cat_id){
if ( !cat_id ) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if ( !cat_obj ) return null;
return cat_obj.cat_name;
};
}]);

I suggest using Jasmine and angular's mock module. You can create a mock of the stateFactory so that it does not hit a web service while unit testing. I have used Sinon to create my mocks and spies. You can, then, have angular inject your mock instead of the real service. This way, the only system under test is the categoryNameByIdFilter and not your web service.
// representation of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function ()
{
return function ()
{
//This is the real stateFactory, which we are going to mock out.
};
}]);
// categoryNameByIdFilter - The system under test in this example
app.factory('categoryNameByIdFilter', ['stateFactory', '_', function (stateFactiry, _)
{
return function (cat_id)
{
if (!cat_id) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if (!cat_obj) return null;
return cat_obj.cat_name;
};
}]);
Given the code above, we can test categoryNameByIdFilter by doing this...
describe("categoryNameByIdFilter", function ()
{
beforeEach(module('YOUR_APP_MODULE'));
beforeEach(function ()
{
//The following line creates a mock of what we expect the state factory to return.
//We're mocking this because it is no the system under test, the filter is.
//A sinon 'stub' is a spy
mockStateFactory = sinon.stub({
categories: [
{ id: 1, cat_name: "some category name" },
{ id: 2, cat_name: "another category name" }
]
});
module(function ($provide)
{
//When Angular asks for a stateFactory, give them this mock instead
$provide.value('stateFactory', mockStateFactory);
});
});
//You can inject a filter using the "inject" method below
it("should filter by id", inject(function (categoryNameByIdFilter)
{
//Wrap categoryNameByIdFilter in a spy so that we can make assertions off of it.
var spy = sinon.spy(categoryNameByIdFilter);
var result = spy(1);
expect(result).toEqual("some category name");
expect(spy.calledBefore(mockStateFactory)).toBeTruthy();
expect(spy.returned("some category name")).toBeTruthy();
sinon.assert.calledOnce(spy);
spy(2);//Returns something besides "some category name"
expect(spy.alwaysReturned("some category name")).not.toBeTruthy();
sinon.assert.calledTwice(spy);
}));
});

Related

Watch Value In Vue.js 3, Equivalent In Pinia?

I have a checkbox list of domain tlds, such as com, net, io, etc. I also have a search text input, where I can drill down the list of 500 or so domains to a smaller amount. For example, if I start to type co in to my search text input, I will get back results that match co, such as co, com, com.au, etc. I am using Laravel and Vue,js 3 to achieve this with a watcher. It works beautifully. How can an achieve the same within a Pinia store?
Here is my code currently:
watch: {
'filters.searchedTlds': function(after, before) {
this.fetchsearchedTlds();
}
},
This is inside my vue component.
Next is the code to fetch searched tlds:
fetchsearchedTlds() {
self = this;
axios.get('/fetch-checked-tlds', { params: { searchedTlds: self.filters.searchedTlds } })
.then(function (response) {
self.filters.tlds = response.data.tlds;
console.log(response.data.tlds);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
});
},
And finally, the code inside my Laravel controller:
public function fetchCheckedTlds(Request $request)
{
$data['tlds'] = Tld::where('tld', 'LIKE','%'.$request->input('searchedTlds').'%')->pluck('tld');
return response()->json($data);
}
I am converting my code to use a Pinia store and I am stuck on how to convert my vue component watcher to Pinia?
Many thanks in advance.
To watch a pinia status, you may watch a computed attribute based on pinia or use watch getter
Your pinia may look like the one below.
~/store/filters.js
export const useFilters = defineStore('filters', {
state: () => {
return {
_filters: {},
};
},
getters: {
filters: state => state._filters,
},
...
}
In where you want to watch
<script setup>
import { computed, watch } from 'vue';
import { useFilters } from '~/store/filters.js';
const filters = useFilters();
// watch a computed attributes instead
const searchedTlds = computed(() => {
return filters.filters?.searchedTlds || '';
});
watch(
searchedTlds,
(newValue, oldValue) {
fetchsearchedTlds();
}
);
// or use watch getter
watch(
() => {
return filters.filters?.searchedTlds || '';
},
(newValue, oldValue) {
fetchsearchedTlds();
}
);
</script>
The first parameter of watch() can be a single ref or a getter function, or an array of getter functions, for more details, please view the Watch Source Types.

How to sanitize data when using bookshelf directly in Strapi

Since there seems to be no support in Strapi for an OR clause yet ( https://github.com/strapi/strapi/issues/3194 ), I'm using Bookshelf directly like:
const result = await strapi.query('friendship')
.find({
where: { user1: 1 },
orWhere: { user2: 1 }
})
.fetchAll()
Now, usually you can sanitize your data using:
sanitizeEntity(entities, { model: strapi.models.friendship });
but that won't work here, since we basically left the abstraction of Strapi, right? Could I still somehow sanitize the data by comparing it to the model or something like that?
Since you're accessing bookshelf directly, you'll need to convert the result set to json. Bookshelf has a toJSON helper for this. sanitizeEntity as the name implies, can only be used on a single entity result.
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async findFriends(){
const entities = await strapi.query('friendship')
.find({
where: { user1: 1 },
orWhere: { user2: 1 }
})
.fetchAll()
.then(results => results.toJSON());
return entities.map(entity => sanitizeEntity( {
model: strapi.models.friendship
} ))
}
}
If you want to dig deeper, you can inspect your node_modules for this file
node_modules/strapi/packages/strapi-connector-bookshelf/lib/queries.js. This is how strapi sets up the find service helper for your model.
function find(params, populate, { transacting } = {}) {
const filters = convertRestQueryParams(params);
return model
.query(buildQuery({ model, filters }))
.fetchAll({
withRelated: populate,
transacting,
})
.then(results => results.toJSON());
}

ajax call does not work in angular js

I have the scenario as follow:
I have a text box and button and whenever I add sth in textbox I want to add the text in the table my code is as follow:
var app = angular.module('app', []);
app.factory('Service', function() {
var typesHash = [ {
id :1,
name : 'lemon',
price : 100,
unit : 2.5
}, {
id : 2,
name : 'meat',
price : 200,
unit : 3.3
} ];
var localId = 3;
var service = {
addTable : addTable,
getData : getData,
};
return service;
function addTable(name) {
typesHash.push({id:localId++, name:name, price:100,unit:1});
}
function getData() {
return typesHash;
}
});
app.controller('table', function(Service) {
//get the return data from getData funtion in factory
this.typesHash = Service.getData();
//get the addtable function from factory
this.addTable = Service.addTable;
});
and the plnkr is as follow:
plnkr
Now as you can see I add whatever inside the text in the table and everything works fine but now I want to add whatever inside the textbox and also I want to get some information from the servlet and add those to the table as well. so for that I use ajax call as follow:
function addTable(name) {
typesHash.push({id:localId++, name:name, price:100,unit:1});
var responsePromise = $http.get("http://localhost:8080/purchase/AddInfo");
responsePromise.success(function(data, status, headers, config) {
typesHash.push( {id:data.id,name : data.name, price : data.price,unit:2.5 });
});
}
but when I use that I get he following error:
ReferenceError: $http is not defined
can anyone help? (just a quick note: this code is smaller version of my real code and I purposely used factory since I need it)
inside of your controller attr your should insert an $http argument:
app.controller('CTRL1', function($scope, $http){
//Now your can use $http methods
})
or insert $http argument in your service decleration if you are using $http request methods from inside of your service

canjs findOne deferred

I learned that instead of using model.findAll and write code in call back function of findAll we can achieve same by using new model.List({}).
E.g., jsfiddle --> http://jsfiddle.net/CRZXH/48/ .. in this jsfiddle example List implementation works but findOne fails.
var people = new Person.List({});
return can.Component({
tag: 'people',
template: initView,
scope: {
people: people
}
})
Above example works fine, initially people is assigned with empty object but after ajax call complete people variable is updated with list and view updates on its own.
How to achieve the same in case of findOne?
var person = PersonModel.findOne({});
can.Component({
tag: 'person',
template: initView,
scope: person
})
This fails....
I did work around as below :
var person;
PersonModel.findOne({},function(data){
person = data
});
can.Component({
tag: 'person',
template: initView,
scope: person
})
This works only if I add asyn=false in findeOne ajax call.
I got solution for this problem from http://webchat.freenode.net/ #daffl
Solution : http://jsfiddle.net/CRZXH/49/
can.Model.extend('Person', {
findOne: 'GET api/metadata',
getMetadata: function() {
var result = new Person();
Person.findOne().then(function(data) {
result.attr(data.attr(), true);
});
return result;
}
}, {});
// Person component which uses findOne
can.Component({
tag: 'person',
scope: function() {
return {
person: Person.getMetadata()
}
}
})
1- the ID for findOne is mandatory
findOne({id: modelId})
2- You can put the person model in a viewmodel (AKA component scope) and not passe the value use can.stache plugin and can.map.define plugin for this
can.fixture({
"GET api/people":function(){
return [
{id: 1, name: "Person 1"},
{id: 2, name: "Person 2"}
];
},"GET api/people/{id}":function(request,response){
return {id: request.data.id, name: "Person "+request.data.id}
}
});
can.Model.extend('Person',{
findAll: 'GET api/people',
findOne: 'GET api/people/{id}',
},{});
can.Component.extend({
tag:'x-person',
scope:{
define:{
person:{
get:function(currentPerson,setFn){
Person.findOne({id: 2}, setFn);
}
}
}
}
});
var frag=can.view('personTmpl',{});
$('#main').html(frag);
Here is the fiddle http://jsfiddle.net/cherif_b/egq85zva/

BackboneJS and Model/Collection

Have json-data like this:
{ tournaments: [ {
tournament_id: "..."
tournament_name: "..."
events: [ {
event_id: ...
event_name: ....
param : [ {
param_a :
param_b : ..
subparan : [ {
sub_1: 1
sub_2 : 2...
So. I don't understand - how to it implement into BackBone Collection/Model style?
How to handle change sub_1? - Made Collection of Collection of Collection?
Simpliest way described in backbone tutorial:
var Events = Backbone.Collection.extend({
initialize: function(){
this.params = new Params()
}
})
var Tournaments = Backbone.Model.extend({
initialize: function(){
this.events = new Events()
}
})
var tournaments = new Tournaments()
You can continue nesting by you needs. When I was working on similar task I wrap each collection in model representing collection state and change itself in answer of collection events. This allows not to asking nested collections about its state having actual state in model.
var CollModel = Backbone.Model.extend({
defaults: {
state = ''//or list or dict or whatever
},
initialize: function(){
this.items = new Backbone.Collection();
this.items.on('all', this.setState, this)
},
setState: function(){
this.set(
'state',
this.items.reduce(function(state, item){
/*calculate state*/
}, '')
)
},
info: function(){
return this.get('state')
}
})
So you can nest collection-models with similar technic and read their state directly through instance.info() depends on how you calculate it. Your top model state will be updated from cascade updates of underneath models-collections.

Resources