test broke when changed model with interfaces to classes - json

Sorry if its a bit long but I wanted to explain myself well.
I have a test for an api call im performing from my service to receive a simple json. (Karma-jasmin/angular2)
this is the call from my service:
#Injectable()
export class MyService {
constructor(private _myApiService:MyAPIService) {
}
public getCarsData():Observable<Car[]> {
return this._myApiService.getCurrentCarData().map(res => res.carList);
}
}
this is getCurrentCarData():
export interface CarsListResponse extends ServerResponse {
carList: Cars[];
}
public getCurrentCarData(): Observable<CarsListResponse> {
let apiURL = 'my/api/url'
return this.http.get(apiURL),
(ret, retJson) => ret.status === 200 ? {carList: retJson.cars.map(element => new Car(element.year, element.make))} : undefined);
}
Car interface is:
export interface Car {
make:string;
year:number;
}
the json Im getting looks like this (part of a mock):
status: 200,
headers: new Headers(HEADERS),
body: {
cars: [
{
"make": "Mercedes",
"year": 2016
},
{
"make": "Audi",
"year": 2017
},
{
"make": "BMW",
"year": 2015
}
]
}
Test: the test is for getCurrentCarData():
let carsResponse = () => {
return { cars: [
{
"make": "Mercedes",
"year": 2016
},
{
"make": "Audi",
"year": 2017
},
{
"make": "BMW",
"year": 2015
}
]}
};
let carsExpectedResponse = () => {
return [
{
"make": "Mercedes",
"year": 2016
},
{
"make": "Audi",
"year": 2017
},
{
"make": "BMW",
"year": 2015
}
]
};
describe('GET Car Data', () => {
it('should handle respond', inject([XHRBackend, api.MyApiService], (mock, myApiService) => {
let c:Connection = null;
mock.connections.subscribe((connection) => {
connection.mock(new Response(new ResponseOptions({
status: 200,
headers: jsoHeaders(),
body: carsResponse()
})));
c = connection;
});
myApiService.getCurrentCarData().subscribe(
res => {
expect(c.request.url).toEqual(`my/api/url`);
expect(res.carList).toEqual(carsExpectedResponse());
},
error => {
expect(false).toBeTruthy();
}
);
}));
});
Ok, so this works! THE problem is when I changed my model from this
interface:
export interface Car {
make:string;
year:number;
}
to this class:
export class Car implements GenType {
make:string;
year:number;
constructor(make:string, year:number) {
this.make = make;
this.year = year;
}
displayFormat:() => string = function () {
return 'someStr'
}
}
export interface GenType {
displayFormat: () => string;
}
so now the error im getting is:
Expected
[MatcherEntityBulk({ displayFormat: Function, make: 'Mercedes', year: 2016 }),
MatcherEntityBulk({ displayFormat: Function, make: 'Audi', year: 2017 }),
MatcherEntityBulk({ displayFormat: Function, make: 'BMW', year: 2015 })]
to equal
[Object({ displayFormat: Function, make: 'Mercedes', year: 2016 }),
Object({ displayFormat: Function, make: 'Audi', year: 2017 }),
Object({ displayFormat: Function, make: 'BMW', year: 2015 })]
So the problem is pretty obvious, but how do I fix this, I mean with the current change what would be a proper way to change that test?
thanks a bunch for the ones that survived to this point :)

toEqual expects carsExpectedResponse() array elements to be instances of certain class (for example, Car). It tests object constructors and expects them to have non-enumerable property constructor that contains a constructor.
It can be a real instance:
let carsExpectedResponse = () => [
new Car(...),
...
];
It can be a fake non-enumerable constructor property:
let carsExpectedResponse = () => [
{
"make": "Mercedes",
"year": 2016
},
...
].map(obj => Object.defineProperty(obj, 'constructor', { value: Car }));
It can be an object constructed with desired prototype chain:
let carsExpectedResponse = () => [
{
"make": "Mercedes",
"year": 2016
},
...
].map(obj => Object.assign(Object.create(Car.prototype), obj));
The last fixture object is probably the most solid, because it doesn't depend on Jasmine internal logic and doesn't depend on constructor logic, too.

Related

Angular 13 - How to get pager details from the JSON response

I work on Angular 13 and I face an issue in that I can't retrieve pager data from the JSON.
The items array returned success but I can't return pager details.
So how to do it?
{
"items": [
{
"id": 3,
"itemNameER": "قلم",
"itemNameEN": "pen",
"description": "1"
},
{
"id": 4,
"itemNameER": "قلم",
"itemNameEN": "pencil",
"description": null
},
{
"id": 5,
"itemNameER": "قلم",
"itemNameEN": "pen2",
"description": null
},
{
"id": 8,
"itemNameER": "car",
"itemNameEN": "car",
"description": "1"
},
{
"id": 9,
"itemNameER": "mobile",
"itemNameEN": "mobile",
"description": "1"
}
],
"pager": {
"numberOfPages": 2,
"currentPage": 1,
"totalRecords": 6
}
}
What I had try is:
items?:ItemsData[];
export interface ItemsData {
id:number;
itemNameER:string;
itemNameEN:string;
description:string;
}
retrieveAllItems(pageNumber: number = 0): void {
this.erpservice.getAll(pageNumber)
.subscribe(
data => {
this.items=data.items;
console.log(data);
},
error => {
console.log(error);
});
}
How to extract pager data from JSON for the numberOfPages, currentPage and totalRecords?
Updated post
This is the information for the getAll return type.
So how to get pager data details?
export interface DataWrapper {
items: ItemsData[];
}
getAll(pageNumber: number): Observable<DataWrapper> {
let params = new HttpParams();
if (pageNumber)
params = params.append('pageNumber', pageNumber);
let httpOptions = {
params: params
};
return this.http.get<DataWrapper>(baseUrl,httpOptions);
}
What I had try is:
pager: any;
this.pager = data.pager;
But I get an error:
Property 'pager' does not exist on type 'DataWrapper'.ts(2339)
<ul>
<li *ngFor="let item of items | paginate: { currentPage:pager.currentPage }; let i = index">
{{ pager.numberOfPages * (pager.currentPage - 1) + i }}
</li>
</ul>
The error message is quite clear. The DataWrapper interface doesn't have a pager property.
You need to:
Add the pager property into DataWrapper interface.
Define the IPager interface.
export interface DataWrapper {
items: ItemsData[];
pager: IPager;
}
export interface IPager {
numberOfPages: number;
currentPage: number;
totalRecords: number;
}

Map JSON for Chartjs with Angular 7

Im trying to map JSON Data to show it in a Bar-Chart. The final Array I need has to look like this:[883, 5925, 17119, 27114, 2758].
Actually, the Array I want to use to set the barChartData (dringlichkeitenValues[])seems to be empty. Sorry for my bad coding skills. Can anyone show me how to solve this Problem?
JSON:
[{
"id": 1,
"value": 883
},
{
"id": 2,
"value": 5925
},
{
"id": 3,
"value": 17119
},
{
"id": 4,
"value": 27144
},
{
"id": 5,
"value": 2758
}]
api.service.ts
getDringlichkeiten(): Observable<IDringlichkeit[]> {
return this.http.get<IDringlichkeit[]>(this.ROOT_URL + '/aufenthalte/dringlichkeit');}
dringlichkeit.ts
export interface IDringlichkeit {
id: number;
value: number;
}
bar-chart.component.ts
export class BarChartComponent implements OnInit {
public dringlichkeitValues:number[] = [];
public dringlichkeiten: IDringlichkeit[];
public barChartLabels:String[] = ["1", "2", "3", "4", "5"];
public barChartData:number[] = this.dringlichkeitValues;
public barChartType:string = 'bar';
constructor(private aufenthaltService: AufenthaltService) {
}
ngOnInit() {
this.loadData();
this.getDringlichkeitValues();
}
loadData(){
this.aufenthaltService.getDringlichkeiten()
.subscribe( data => this.dringlichkeiten = data);
}
getDringlichkeitValues(){
let dringlichkeitValues:number[]=[];
this.dringlichkeiten.forEach(dringlichkeit=>{
dringlichkeitValues.push(dringlichkeit.value)
this.dringlichkeitValues = dringlichkeitValues;
});
return this.dringlichkeitValues;
}
}
UPDATE:
I updated my component but now my Array is still empty after subscribing to the Observable.
bar-chart.component.ts
chart: Chart;
dringlichkeiten: IDringlichkeit[] = [];
constructor(private aufenthaltService: AufenthaltService) {
}
ngOnInit() {
this.aufenthaltService.getDringlichkeiten()
.subscribe( data => {
this.dringlichkeiten = data;
//dringlichkeiten-Array full
console.log(this.dringlichkeiten);
});
//dringlichkeiten-Array empty
console.log(this.dringlichkeiten);
this.chart = new Chart('canvas', {
type: 'bar',
data: {
labels: this.dringlichkeiten.map(x => x.id),
datasets: [
{
label: 'Dringlichkeiten',
data: this.dringlichkeiten.map(x => x.value),
backgroundColor: ['#FF6384', '#4BC0C0', '#FFCE56', '#E7E9ED', '#36A2EB']
}
]
},
});
}
To get the "values" from your JSON array, you can use:
dringlichkeiten.map(x => x.value)
This will get you an array you require, i.e.:
[883, 5925, 17119, 27114, 2758]
You can then pass this array to chartJS for it to render you a chart like so:
this.chart = new Chart('canvas', {
type: 'bar',
data: {
labels: dringlichkeiten.map(x => x.id),
datasets: [
{
label: 'My Bar Chart',
data: dringlichkeiten.map(x => x.value),
backgroundColor: ['red', 'green', 'yellow', 'blue', 'orange']
}
]
},
});
Take a look at this simplified working SlackBlitz example.
Hope this helps!

Angular 5 Observable mapping to Json array

My backend return this :
{
"FirstResponse": [
{
"MyField1": "AAA",
"MyField2": "AAAAAAA"
},
{
"MyField1": "BBB",
"MyField2": "BBBBBBB"
},
{
"MyField1": "CCC",
"MyField2": "CCCCC"
}
],
"SecondResponse": [
{
"FirstName": "FirstNameA",
"LastName": "LastNameA"
},
{
"FirstName": "FirstNameB",
"LastName": "LastNameB"
}
]
}
I'd like map FirstReponse to a variable and SecondResponse to another variable.
How can I adapt the code below ?
search(): Observable<any> {
let apiURL = `......`;
return this.http.get(apiURL)
.map(res => res.json())
}
Update : Excepted result
In one variable this :
[
{
"MyField1": "AAA",
"MyField2": "AAAAAAA"
},
{
"MyField1": "BBB",
"MyField2": "BBBBBBB"
},
{
"MyField1": "CCC",
"MyField2": "CCCCC"
}
]
In a second :
[
{
"FirstName": "FirstNameA",
"LastName": "LastNameA"
},
{
"FirstName": "FirstNameB",
"LastName": "LastNameB"
}
]
You could create a new file which exports the model class and then assign it to the returning Observable type. Something like:
new model.ts file
class FieldModel {
Field1: string;
Field1: string;
}
export class valuesModel {
MyValues: Array<FieldModel>;
}
on the service.ts
import { valuesModel } from 'model';
search(): Observable<valuesModel> {
let apiURL = `https://jsonplaceholder.typicode.com/users`;
return this.http.get(apiURL)
.map(res => res.json())
}
Please check this approach, use
import { Http, Response} from '#angular/http';
import { Observable } from 'rxjs/Observable';
public search(){
let apiURL = `https://jsonplaceholder.typicode.com/users`;
return this.http.get(apiURL)
.map((res: Response)=> return res.json();)
.catch((error: Response) => {
return Observable.throw('Something went wrong');
});
}
for this search() method you can subscribe from your component.
And if you want to map output into respected modal then please provide format of same.So that i can help
I don't crealry understan what you wanna get because you not provide example result,
however try this - change line:
.map(res => res.json())
to
.map(res => res.json().MyValues )
using this you will get at the top level similar array like in link you provided in comment below you question: https://jsonplaceholder.typicode.com/users
UPDATE (after question update 9.10.2018)
Currently .map(res => res.json()) returns object that has two fields (variables) "FirstResponse" and "SecondResponse". You can have acces to it by for example (I write code from head):
public async loadData()
{
let data = await this.yourService.search().toPromise();
let firstVariable = data.FirstResponse;
let secondVariable = data.SecondResponse;
...
}
So as you describe in your question/comments in loadData() you get result in two variables as you want.
Or alternative answer - if you wanna do this inside search() then you can do that in such way for example:
search(): Observable<any> {
let apiURL = `......`;
return this.http.get(apiURL)
.map( (res) => {
let data = res.json();
return {
firstVariable: data.FirstResponse,
secondVariable: data.SecondResponse,
}
})
}

Getting json object data with react

I am attempting to pull data out of json like this, which is imported as "values"
{
"content": {
"person": [
{
"name": "Test"
"age" : "24:
}
]
}
}
I am using .map like below but getting the error .default.map is not a function I believe it is because i have objects not arrays, i've tried a bunch of stuff including object.keys but i'm getting errors all over the place, any direction would be appreciated.
import values from './sample.json'
const vals = values.map((myval, index) => {
const items = person.items.map((item, i) => {
return (
<div>{item.name}</div>
)
})
return (
<div>{items}</div>
)
})
I think your data and code have some errors. But after fixing those and also changing the name from 'person' to 'people' if that's what you are after, here's the code that does what you are trying to do:
var data = {
content: {
people: [
{
name: "Test",
age: 24,
},
{
name: "Foo",
age: 25,
},
],
},
};
var App = React.createClass({
render: function () {
var people = data.content.people.map(function (person) {
return <div>{person.name}</div>;
});
return <div>{people}</div>;
},
});
ReactDOM.render(<App />, document.getElementById("app"));
And here's the JSBin for that: https://jsbin.com/coyalec/2/edit?html,js,output
Update: I'm updating the answer with more detailed example. It now deals with data more generically, like it doesn't assume what are the entries of 'contents' and such, but it knows that each type like 'people' or 'pets' are an array.
var data = {
content: {
people: [
{
name: "Test",
age: 24,
},
{
name: "Foo",
age: 25,
},
],
pets: [
{
name: "Sweety",
age: 3,
},
{
name: "Kitty",
age: 5,
},
],
},
};
var App = React.createClass({
render: function () {
// Get the keys in data.content. This will return ['people', 'pets']
var contentKeys = Object.keys(data.content);
// Now start iterating through these keys and use those keys to
// retrieve the underlying arrays and then extract the name field
var allNames = contentKeys.map((t) =>
data.content[t].map((e) => <div>{e.name}</div>)
);
return <div>{allNames}</div>;
},
});
ReactDOM.render(<App />, document.getElementById("app"));
And here's the latest JSBin: https://jsbin.com/coyalec/4/edit?html,js,output

JSON stringify in Node JS not serializing array of objects

I am using sails.js (node js framework).
I am trying to JSON.stringify one of the objects, but when I do that it omits one of the fields (rooms array below).
Here is what console.log(object) gives me:
[ { rooms: [ [Object], [Object] ],
state: '53df76c278999310248072c6',
name: 'Sydney Center',
menuItems: null,
createdAt: Mon Aug 04 2014 23:42:08 GMT+0300 (Jerusalem Summer Time),
updatedAt: Mon Aug 04 2014 23:42:08 GMT+0300 (Jerusalem Summer Time),
id: '53dff0205c89c03428a31cee' },
{ rooms: [ [Object], [Object], [Object] ],
state: '53df76c278999310248072c6',
createdAt: Mon Aug 04 2014 23:43:21 GMT+0300 (Jerusalem Summer Time),
menuItems: null,
name: 'Batata Center',
updatedAt: Mon Aug 04 2014 23:51:11 GMT+0300 (Jerusalem Summer Time),
id: '53dff06a5c89c03428a31cf3' } ]
JSON output (rooms are not printed):
[
{
"state": "53df76c278999310248072c6",
"name": "Sydney Center",
"menuItems": null,
"createdAt": "2014-08-04T20:42:08.885Z",
"updatedAt": "2014-08-04T20:42:08.885Z",
"id": "53dff0205c89c03428a31cee"
},
{
"state": "53df76c278999310248072c6",
"createdAt": "2014-08-04T20:43:21.999Z",
"menuItems": null,
"name": "Batata Center",
"updatedAt": "2014-08-04T20:51:11.740Z",
"id": "53dff06a5c89c03428a31cf3"
}
]
What might be the problem?
The rooms data seems to be fine.
For the complete function (SailsJS):
getCentersData: function(req, res) {
sails.log.info('Teacher.getCentersData: ', req.user.id);
var userId = req.user.id;
async.auto({
teacher: function(cb) {
Teacher.findOne({ user: userId }).populate('centers').exec(cb);
},
centers: [
'teacher', function(cb, results) {
var allCentersIds = _.pluck(results.teacher.centers, 'id');
Center.findById(allCentersIds).populate('rooms').exec(cb);
}
],
rooms: [
'centers', function(cb, results) {
var allRoomIds = [];
_.each(results.centers, function(center) {
allRoomIds = _.union(allRoomIds, _.pluck(center.rooms, 'id'));
});
Room.findById(allRoomIds).populate('children').exec(cb);
}
],
children: [
'rooms', function(cb, results) {
var allChildrenIds = [];
_.each(results.rooms, function (room) {
allChildrenIds = _.union(allChildrenIds, _.pluck(room.children, 'id'));
});
Child.findById(allChildrenIds).populate('parents').exec(cb);
}
],
parentUsers: ['children', function(cb, results) {
var allParentIds = [];
_.each(results.children, function (child) {
allParentIds = _.union(allParentIds, _.pluck(child.parents, 'id'));
});
Parent.findById(allParentIds).populate('user').exec(cb);
}],
map: ['parentUsers', function (cb, results) {
// map children to parents
var parentsMapper = _.indexBy(results.parentUsers, 'id');
var childrenMappedToParents = _.map(results.children, function (child) {
var _child = child.toObject();
_child.parents = _.map(child.parents, function (parent) {
return parentsMapper[parent.id];
});
return _child;
});
var childrenMapper = _.indexBy(childrenMappedToParents, 'id');
// map rooms to children
var roomsMappedToChildren = _.map(results.rooms, function (room) {
var _room = room.toObject();
_room.children = _.map(room.children, function (child) {
return childrenMapper[child.id];
});
return _room;
});
var roomsMapper = _.indexBy(roomsMappedToChildren, 'id');
// map center to rooms
var centersMappedToRooms = _.map(results.centers, function (center) {
var _center = center.toObject();
_center.rooms = _.map(center.rooms, function (room) {
return roomsMapper[room.id];
});
return _center;
});
sails.log.info('centersMappedToRooms',centersMappedToRooms ); // includes rooms array
sails.log.info('centersMappedToRooms json: ', JSON.stringify(centersMappedToRooms)); // does not include rooms array
return cb(null, centersMappedToRooms);
}]
}, function(err, results) {
if (err) {
return res.serverError(err);
}
// added prints
sails.log.info("results.map: ", results.map);
sails.log.info("JSON.stringify(results.map): ", JSON.stringify(results.map)); // same same, does not print the rooms array
return res.json(results.map);
});
},
EDITED
Schema:
schema: true,
attributes: {
name: {
type: 'string',
required: true,
minLength: 5
},
// Many-To-Many association with Teacher model
teachers: {
collection: 'teacher',
via: 'centers'
},
// One-To-Many association with State model
state: {
model: 'state'
},
// One-To-Many association with Room model
rooms: {
collection: 'room',
via: 'center'
},
// One-To-One association with Principal model
principal: {
model: 'principal'
},
menuItems: {
type: 'array',
defaultsTo: null
}
},
Because Waterline queries return models, not plain javascript objects, they have additional properties and functions on them. One of these is an overridden toJSON() function which removes attributes that have not been populated. What seems to be happening here is that you are attaching objects to a parent model which doesn't know it has children that have been populated so it strips off the values.
The reasoning behind this is so that if you query for all Users and don't populate Rooms you don't get an incorrect result showing an empty rooms array.
I'm not sure what all you are manipulating here but the reason it works if you _.cloneDeep is because it removes the custom toJSON field. This is the recommend strategy when you are mutating a parent object from a query like this.