diff --git a/cypress/e2e/attributes.page.cy.js b/cypress/e2e/attributes.page.cy.js index 34c03b1..350ffff 100644 --- a/cypress/e2e/attributes.page.cy.js +++ b/cypress/e2e/attributes.page.cy.js @@ -7,7 +7,8 @@ describe('Attributes Page', () => { cy.intercept('GET', 'api/attributes/' + userId, (req) => { req.reply({ statusCode: 200, - body: [{"phrase":{"id":3,"adverb":"","verb":"sculpts","preposition":"with","noun":"clay"},"userCount":1},{"phrase":{"id":2,"adverb":"","verb":"plays","preposition":"","noun":"chess"},"userCount":2}] + body: [{"phrase":{"id":3,"adverb":"","verb":"sculpts","preposition":"with","noun":"clay"},"sequence":1,"userCount":1}, + {"phrase":{"id":2,"adverb":"","verb":"plays","preposition":"","noun":"chess"},"sequence":2,"userCount":2}] }); }); @@ -117,4 +118,49 @@ describe('Attributes Page', () => { }) }) }) + describe('Reorder Attributes', () => { + + it('should move an attribute up the list', () => { + cy.goToAttributesPage(); + cy.get('[data-test="attributeItem"]').eq(1).click(); + cy.get('[data-test="upButton"]').click(); + cy.get('[data-test="attributeItem"]').eq(0).contains(' plays chess (2 users)x'); + cy.get('[data-test="attributeItem"]').eq(1).contains( ' sculpts with clay (1 users)x'); + }); + + it('should move an attribute down the list', () => { + cy.goToAttributesPage(); + cy.get('[data-test="attributeItem"]').eq(0).click(); + cy.get('[data-test="downButton"]').click(); + cy.get('[data-test="attributeItem"]').eq(0).contains(' plays chess'); + cy.get('[data-test="attributeItem"]').eq(1).contains( ' sculpts with clay (1 users)x'); + }); + + it('should disable the "Up" button for the first item', () => { + cy.goToAttributesPage(); + cy.get('[data-test="attributeItem"]').eq(0).click(); + cy.get('[data-test="upButton"]').should('have.attr','disabled'); + }); + + it('should disable the "Down" button for the last item', () => { + cy.goToAttributesPage(); + cy.get('[data-test="attributeItem"]').eq(1).click(); + cy.get('[data-test="downButton"]').should('have.attr','disabled'); + }); + + it('should enable and save changes after reordering', () => { + cy.goToAttributesPage(); + cy.get('[data-test="attributeItem"]').eq(1).click(); + cy.get('[data-test="upButton"]').click(); + cy.get('[data-test="saveButton"]').should('not.be.disabled'); + cy.intercept('POST', '/api/attributes/update', { + statusCode: 200, + body: { success: true } + }).as('saveAttributesOrder'); + cy.get('[data-test="saveButton"]').click(); + cy.wait('@saveAttributesOrder'); + }); + }); + + }) diff --git a/src/app/pages/attributes-page/_services/attributes.api.service.ts b/src/app/pages/attributes-page/_services/attributes.api.service.ts index 08ad6dc..46a458c 100644 --- a/src/app/pages/attributes-page/_services/attributes.api.service.ts +++ b/src/app/pages/attributes-page/_services/attributes.api.service.ts @@ -48,10 +48,8 @@ export class AttributesApiService { ); }); } - saveSequence(data: {phrases: {sequence: number, phraseId: number}[]}) { + saveAttributeSequences(data: {phrases: {sequence: number, phraseId: number}[]}) { const url = environment.apiUrl + '/api/attributes/update'; - console.log(data); - return new Promise((resolve, reject) => { this._apiService.post(url, data).subscribe( (response: boolean) => { diff --git a/src/app/pages/attributes-page/_services/attributes.model.service.ts b/src/app/pages/attributes-page/_services/attributes.model.service.ts index efb4bad..9966f61 100644 --- a/src/app/pages/attributes-page/_services/attributes.model.service.ts +++ b/src/app/pages/attributes-page/_services/attributes.model.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import {AuthService, JWTApiService} from '@savvato-software/savvato-javascript-services'; +import {AuthService, JWTApiService, SequenceService} from '@savvato-software/savvato-javascript-services'; import { AttributesApiService } from "./attributes.api.service"; import {Attribute} from '../../../_type/attribute.type' @@ -13,10 +13,12 @@ export class AttributesModelService { model:Attribute[] = []; originalAttributes: Attribute[] = []; + selectedAttr: Attribute | null = null; constructor(private _apiService: JWTApiService, private _authService: AuthService, private _attributesApiService: AttributesApiService, + private _sequenceService: SequenceService, private _constants: Constants) { } @@ -37,13 +39,45 @@ export class AttributesModelService { get() { - return this.model; + return this.model.sort((a, b) => a.sequence - b.sequence); } isDirty(): boolean{ return JSON.stringify(this.model) !== JSON.stringify(this.originalAttributes); } + setSelectedAttr(attr: Attribute) { + this.selectedAttr = attr; + } + + isSelected(attr: Attribute): boolean { + return this.selectedAttr && attr && (this.selectedAttr.phrase.id === attr.phrase.id); + } + + canMoveUp() { + if (this.model.length > 0 && this.selectedAttr) { + return this._sequenceService.isAbleToMove(this.model, this.selectedAttr, this._sequenceService.UP); + } + + return false; + } + + canMoveDown() { + if (this.model.length > 0 && this.selectedAttr) { + return this._sequenceService.isAbleToMove(this.model, this.selectedAttr, this._sequenceService.DOWN); + } + + return false; + } + + moveUp() { + this._sequenceService.moveSequenceByOne(this.model, this.selectedAttr, this._sequenceService.UP); + } + + moveDown() { + this._sequenceService.moveSequenceByOne(this.model, this.selectedAttr, this._sequenceService.DOWN); + } + save(model:Attribute) { return new Promise((resolve, reject) => { this._attributesApiService.save(model).then( @@ -58,15 +92,15 @@ export class AttributesModelService { }); } - saveAttributeSequence() { + saveAttributeSequences() { return new Promise((resolve, reject) => { - let phrase = this.model.map(item => {return { + let listOfPhraseIdSequencePairs = this.model.map(item => {return { 'sequence': item.sequence, 'phraseId': item.phrase.id }; }) - let data = {'phrases': phrase} - this._attributesApiService.saveSequence(data).then( + let data = {'phrases': listOfPhraseIdSequencePairs} + this._attributesApiService.saveAttributeSequences(data).then( (booleanMessage: boolean) => { resolve(booleanMessage); console.log("Call to attributeApiService was successful(model) + (booleanMessage)") diff --git a/src/app/pages/attributes-page/attributes.page.html b/src/app/pages/attributes-page/attributes.page.html index 48f0124..386c981 100644 --- a/src/app/pages/attributes-page/attributes.page.html +++ b/src/app/pages/attributes-page/attributes.page.html @@ -1,13 +1,12 @@ - Up - Down - Save + Up + Down + Save - - + {{attr['phrase']['adverb'] || ''}} {{attr['phrase']['verb'] || ''}} {{attr['phrase']['preposition'] || ''}} {{attr['phrase']['noun'] || ''}} ({{attr['userCount'] || '0'}} users) diff --git a/src/app/pages/attributes-page/attributes.page.ts b/src/app/pages/attributes-page/attributes.page.ts index f0e3308..3765f4d 100644 --- a/src/app/pages/attributes-page/attributes.page.ts +++ b/src/app/pages/attributes-page/attributes.page.ts @@ -8,7 +8,6 @@ import { LoadingService } from "../../_services/loading-spinner/loading.service" import { AttributesModelService } from "./_services/attributes.model.service"; import { SequenceService, Sequenceable } from '@savvato-software/savvato-javascript-services'; import {Attribute} from '../../_type/attribute.type' -import {Phrase} from '../../_type/phrase.type' @Component({ selector: 'app-attributes', @@ -28,7 +27,6 @@ export class AttributesPage implements OnInit { private _loadingService: LoadingService, private _attributesModelService: AttributesModelService, private router: Router, - private sequenceService: SequenceService, ) {} public ngOnInit() { @@ -43,49 +41,43 @@ export class AttributesPage implements OnInit { }); } - get attributes(): Attribute[] { - const attributes: Attribute[] = this._attributesModelService.get(); - return attributes; - } - getAttributes():Attribute[] { const attributes: Attribute[] = this._attributesModelService.get(); return attributes; } selectAttribute(attr: Attribute) { - this.selectedAttr = attr; + this._attributesModelService.setSelectedAttr(attr); + } + + isSelected(attr: Attribute): boolean { + return this._attributesModelService.isSelected(attr); } canMoveUp(): boolean { - return this.selectedAttr && this.selectedAttr.sequence > 1; + return this._attributesModelService.canMoveUp(); } canMoveDown(): boolean { - return this.selectedAttr && this.selectedAttr.sequence < this.attributes.length; + return this._attributesModelService.canMoveDown(); } isSaveEnabled(): boolean { //returns true if there is a change let isDirty = this._attributesModelService.isDirty(); - console.log(isDirty) return isDirty; } moveUp() { - if (this.selectedAttr && this.canMoveUp()) { - this.sequenceService.moveSequenceByOne(this._attributesModelService.get(), this.selectedAttr, this.sequenceService.UP); - } + this._attributesModelService.moveUp(); } moveDown() { - if (this.selectedAttr && this.canMoveDown()) { - this.sequenceService.moveSequenceByOne(this._attributesModelService.get(), this.selectedAttr, this.sequenceService.DOWN); - } + this._attributesModelService.moveDown(); } saveChanges() { - this._attributesModelService.saveAttributeSequence() + this._attributesModelService.saveAttributeSequences() .then((response) => { console.log("Call to attributeApiService was successful"); return this._attributesModelService.init()