Skip to content

Commit

Permalink
chore(quest-preview): add repeatable and required professions (#637)
Browse files Browse the repository at this point in the history
* chore(quest-preview): add profession

* chore(quest-preview): wip

* chore(quest-preview): improve integration tests

* fix(test): improve test

* chore(quest-preview): add unit-tests
  • Loading branch information
Helias committed Apr 27, 2020
1 parent 4713b0c commit 4d6985a
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 12 deletions.
Expand Up @@ -23,34 +23,38 @@ <h5 id="title" *ngIf="!!service.title">{{ service.title }}</h5>
{{ service.CLASSES_TEXT[c] }} &nbsp;
</span>
</p>
<p id="requiredSkill" *ngIf="service.requiredSkill$ | async as skill">
Profession: <keira-icon *ngIf="!!service.ICON_SKILLS[service.questTemplateAddon.RequiredSkillID]" [skillId]="service.questTemplateAddon.RequiredSkillID" [size]="'small'"></keira-icon> {{ skill }} {{ reqSkillPoint }}
</p>
<!-- Quest starter -->
<p *ngIf="service.questGivenByItem$ | async as qitem">
<img [src]="'assets/img/quest/' + questStartIcon">
<img [src]="'assets/img/quest/' + questStartIcon" id="questStartIcon">
Start:
<keira-icon [size]="'small'" [itemId]="qitem"></keira-icon>
<strong class="colored"> {{ service.questStarterItem$ | async }}</strong> <span class="greyed"> [{{ qitem }}]</span>
</p>
<div *ngIf="service.creatureQueststarterList.length > 0">
<p *ngFor="let q of service.creatureQueststarterList">
<img [src]="'assets/img/quest/' + questStartIcon"> NPC Start: <strong class="colored">{{ service.mysqlQueryService.getCreatureNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
<img [src]="'assets/img/quest/' + questStartIcon" id="questStartIcon"> NPC Start: <strong class="colored">{{ service.mysqlQueryService.getCreatureNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
</p>
</div>
<div *ngIf="service.gameobjectQueststarterList.length > 0">
<p *ngFor="let q of service.gameobjectQueststarterList">
<img [src]="'assets/img/quest/' + questStartIcon"> GO Start: <strong class="colored">{{ service.mysqlQueryService.getGameObjectNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
<img [src]="'assets/img/quest/' + questStartIcon" id="questStartIcon"> GO Start: <strong class="colored">{{ service.mysqlQueryService.getGameObjectNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
</p>
</div>
<!-- Quest ender -->
<div *ngIf="service.creatureQuestenderList.length > 0">
<p *ngFor="let q of service.creatureQuestenderList">
<img [src]="'assets/img/quest/' + questEndIcon"> NPC End: <strong class="colored">{{ service.mysqlQueryService.getCreatureNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
<img [src]="'assets/img/quest/' + questEndIcon" id="questEndIcon"> NPC End: <strong class="colored">{{ service.mysqlQueryService.getCreatureNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
</p>
</div>
<div *ngIf="service.gameobjectQuestenderList.length > 0">
<p *ngFor="let q of service.gameobjectQuestenderList">
<img [src]="'assets/img/quest/' + questEndIcon"> GO End: <strong class="colored">{{ service.mysqlQueryService.getGameObjectNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
<img [src]="'assets/img/quest/' + questEndIcon" id="questEndIcon"> GO End: <strong class="colored">{{ service.mysqlQueryService.getGameObjectNameById(q.id) | async }}</strong> <span class="greyed"> [{{ q.id }}]</span>
</p>
</div>
<p *ngIf="service.isRepeatable()">Repeatable</p>
<p>{{ service.sharable }}</p>

<p *ngIf="service.difficultyLevels; let difficulty">
Expand Down
Expand Up @@ -6,11 +6,20 @@ import { QuestModule } from '../quest.module';
import { RouterTestingModule } from '@angular/router/testing';
import { QuestPreviewService } from './quest-preview.service';
import { PageObject } from '@keira-shared/testing/page-object';
import { QuestTemplateService } from '../quest-template/quest-template.service';
import { QuestTemplateAddonService } from '../quest-template-addon/quest-template-addon.service';

class QuestPreviewComponentPage extends PageObject<QuestPreviewComponent> {
get title() { return this.query<HTMLHeadElement>('#title'); }
get level() { return this.query<HTMLParagraphElement>('#level'); }
get minLevel() { return this.query<HTMLParagraphElement>('#minlevel'); }
get startIcon() { return this.query<HTMLImageElement>('#questStartIcon'); }
get endIcon() { return this.query<HTMLImageElement>('#questEndIcon'); }
get questType() { return this.query<HTMLParagraphElement>('#type'); }
get races() { return this.query<HTMLParagraphElement>('#races'); }
get classes() { return this.query<HTMLParagraphElement>('#classes'); }
get requiredSkill() { return this.query<HTMLParagraphElement>('#requiredSkill'); }
get racesElement() { return this.fixture.nativeElement.querySelector('#races'); }
}

describe('QuestPreviewComponent', () => {
Expand All @@ -27,11 +36,13 @@ describe('QuestPreviewComponent', () => {

function setup() {
const service = TestBed.inject(QuestPreviewService);
const questTemplateService = TestBed.inject(QuestTemplateService);
const questTemplateAddonService = TestBed.inject(QuestTemplateAddonService);
const fixture: ComponentFixture<QuestPreviewComponent> = TestBed.createComponent(QuestPreviewComponent);
const component: QuestPreviewComponent = fixture.componentInstance;
const page = new QuestPreviewComponentPage(fixture);

return { fixture, component, service, page };
return { fixture, component, service, page, questTemplateService, questTemplateAddonService };
}

it('ngOnInit should initialise services', () => {
Expand Down Expand Up @@ -62,4 +73,86 @@ describe('QuestPreviewComponent', () => {
expect(page.minLevel.innerText).toContain(`${minLevel} - ${maxLevel}`);
fixture.debugElement.nativeElement.remove();
});

it('should show questStart and questEnd icons', () => {
const { fixture, service, page } = setup();
const periodicQuestSpy = spyOnProperty(service, 'periodicQuest', 'get').and.returnValue('');
spyOnProperty(service, 'creatureQueststarterList', 'get').and.returnValue([{ id: 123, quest: 123 }]);
spyOnProperty(service, 'creatureQuestenderList', 'get').and.returnValue([{ id: 123, quest: 123 }]);

fixture.detectChanges();

expect(page.startIcon.src).toContain('assets/img/quest/quest_start.gif');
expect(page.endIcon.src).toContain('assets/img/quest/quest_end.gif');

periodicQuestSpy.and.returnValue('Daily');

fixture.detectChanges();

expect(page.startIcon.src).toContain('assets/img/quest/quest_start_daily.gif');
expect(page.endIcon.src).toContain('assets/img/quest/quest_end_daily.gif');

fixture.debugElement.nativeElement.remove();
});

it('should show questType', () => {
const { fixture, service, page, questTemplateService } = setup();
spyOnProperty(service, 'periodicQuest', 'get').and.returnValue('Daily');
questTemplateService.form.controls.QuestInfoID.setValue(41);

fixture.detectChanges();

expect(page.questType.innerText).toContain('Daily');
expect(page.questType.innerText).toContain('PvP');

fixture.debugElement.nativeElement.remove();
});

it('should show showRaces', () => {
const { fixture, service, page } = setup();
const sideSpy = spyOnProperty(service, 'side', 'get').and.returnValue('');
spyOnProperty(service, 'races', 'get').and.returnValue([2, 4]);

fixture.detectChanges();

expect(page.races.innerText).toContain('Orc');
expect(page.races.innerText).toContain('Night Elf');

// in case of "Side"
sideSpy.and.returnValue('Alliance');
fixture.detectChanges();
expect(page.racesElement).toBeFalsy();

fixture.debugElement.nativeElement.remove();
});

it('should show showClasses', () => {
const { fixture, service, page } = setup();
spyOnProperty(service, 'classes', 'get').and.returnValue([2, 4]);

fixture.detectChanges();

expect(page.classes.innerText).toContain('Paladin');
expect(page.classes.innerText).toContain('Rogue');

fixture.debugElement.nativeElement.remove();
});

it('should show required skill', async() => {
const { fixture, service, page, questTemplateAddonService } = setup();
spyOnProperty(service, 'requiredSkill$', 'get').and.returnValue(Promise.resolve('Jewelcrafting'));
questTemplateAddonService.form.controls.RequiredSkillID.setValue(755);

fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();

expect(page.requiredSkill.innerText).toContain('Jewelcrafting');

questTemplateAddonService.form.controls.RequiredSkillPoints.setValue(10);
fixture.detectChanges();
expect(page.requiredSkill.innerText).toContain('(10)');

fixture.debugElement.nativeElement.remove();
});
});
Expand Up @@ -32,6 +32,12 @@ export class QuestPreviewComponent implements OnInit {
: 'quest_end.gif';
}

get reqSkillPoint() {
return !!this.service.questTemplateAddon.RequiredSkillPoints && this.service.questTemplateAddon.RequiredSkillPoints > 1
? `(${this.service.questTemplateAddon.RequiredSkillPoints})`
: '';
}

ngOnInit() {
this.service.initializeServices();
}
Expand Down
17 changes: 16 additions & 1 deletion src/app/features/quest/quest-preview/quest-preview.service.ts
Expand Up @@ -22,7 +22,11 @@ import { GameobjectQuestender } from '@keira-shared/types/gameobject-questender.
import { QuestTemplateAddon } from '@keira-types/quest-template-addon.type';
import { DifficultyLevel } from './quest-preview.model';
import { RACES_TEXT, CLASSES_TEXT } from '@keira-shared/constants/preview';
import { QUEST_FLAG_DAILY, QUEST_FLAG_WEEKLY, QUEST_FLAG_SPECIAL_MONTHLY, QUEST_INFO } from '@keira-shared/constants/quest-preview';
import { SqliteQueryService } from '@keira-shared/services/sqlite-query.service';
import {
QUEST_FLAG_DAILY, QUEST_FLAG_WEEKLY, QUEST_FLAG_SPECIAL_MONTHLY, QUEST_INFO,
QUEST_FLAG_REPEATABLE, QUEST_FLAG_SPECIAL_REPEATABLE, ICON_SKILLS
} from '@keira-shared/constants/quest-preview';

@Injectable()
export class QuestPreviewService {
Expand All @@ -35,6 +39,7 @@ export class QuestPreviewService {
constructor(
private readonly helperService: PreviewHelperService,
public readonly mysqlQueryService: MysqlQueryService,
private readonly sqliteQueryService: SqliteQueryService,
private readonly questHandlerService: QuestHandlerService,
private readonly questTemplateService: QuestTemplateService,
private readonly questRequestItemsService: QuestRequestItemsService,
Expand All @@ -48,6 +53,7 @@ export class QuestPreviewService {
readonly RACES_TEXT = RACES_TEXT;
readonly CLASSES_TEXT = CLASSES_TEXT;
readonly QUEST_INFO = QUEST_INFO;
readonly ICON_SKILLS = ICON_SKILLS;

// get form value
get questTemplate(): QuestTemplate { return this.questTemplateService.form.getRawValue(); }
Expand Down Expand Up @@ -241,4 +247,13 @@ export class QuestPreviewService {
private getEnabledByQuestName(): Promise<string> {
return this.mysqlQueryService.getQuestTitleById(this.getEnabledByQuestId());
}

public isRepeatable(): boolean {
return !!(this.questTemplate.Flags & QUEST_FLAG_REPEATABLE || this.questTemplateAddon.SpecialFlags & QUEST_FLAG_SPECIAL_REPEATABLE);
}

get requiredSkill$(): Promise<string> {
return this.sqliteQueryService.getSkillNameById(Number(this.questTemplateAddon.RequiredSkillID));
}

}
Expand Up @@ -21,6 +21,7 @@ describe('QuestTemplateAddon integration tests', () => {
let component: QuestTemplateAddonComponent;
let fixture: ComponentFixture<QuestTemplateAddonComponent>;
let queryService: MysqlQueryService;
let sqliteQueryService: SqliteQueryService;
let querySpy: Spy;
let handlerService: QuestHandlerService;
let page: QuestTemplateAddonPage;
Expand Down Expand Up @@ -63,15 +64,20 @@ describe('QuestTemplateAddon integration tests', () => {
]
})
.compileComponents();

}));

function setup(creatingNew: boolean) {
handlerService = TestBed.inject(QuestHandlerService);
handlerService['_selected'] = `${id}`;
handlerService.isNew = creatingNew;

sqliteQueryService = TestBed.inject(SqliteQueryService);
queryService = TestBed.inject(MysqlQueryService);
querySpy = spyOn(queryService, 'query').and.returnValue(of());
spyOn(sqliteQueryService, 'query').and.returnValue(of(
[{ ID: 123, spellName: 'Mock Spell' }]
));

spyOn(queryService, 'selectAll').and.returnValue(of(
creatingNew ? [] : [originalEntity]
Expand Down Expand Up @@ -216,11 +222,6 @@ describe('QuestTemplateAddon integration tests', () => {
// https://stackoverflow.com/questions/57336982/how-to-make-angular-tests-wait-for-previous-async-operation-to-complete-before-e

const field = 'SourceSpellID';
const sqliteQueryService = TestBed.inject(SqliteQueryService);
spyOn(sqliteQueryService, 'query').and.returnValue(of(
[{ ID: 123, spellName: 'Mock Spell' }]
));

page.clickElement(page.getSelectorBtn(field));
await page.whenReady();
page.expectModalDisplayed();
Expand Down
20 changes: 20 additions & 0 deletions src/app/shared/constants/quest-preview.ts
Expand Up @@ -16,3 +16,23 @@ export const QUEST_INFO = {
export const QUEST_FLAG_DAILY = 0x01000;
export const QUEST_FLAG_WEEKLY = 0x08000;
export const QUEST_FLAG_SPECIAL_MONTHLY = 0x10;
export const QUEST_FLAG_REPEATABLE = 0x02000;
export const QUEST_FLAG_SPECIAL_REPEATABLE = 0x01;

export const ICON_SKILLS = {
171: 'trade_alchemy', // Alchemy
164: 'trade_blacksmithing', // Blacksmithing
333: 'trade_engraving', // Enchanting
202: 'trade_engineering', // Engineering
182: 'spell_nature_naturetouchgrow', // Herbalism
773: 'inv_inscription_tradeskill01', // Inscription
755: 'inv_misc_gem_01', // Jewelcrafting
165: 'inv_misc_armorkit_17', // Leatherworking
186: 'trade_mining', // Mining
393: 'inv_misc_pelt_wolf_01', // Skinning
197: 'trade_tailoring', // Tailoring
185: 'inv_misc_food_15', // Cooking
129: 'spell_holy_sealofsacrifice', // First Aid
356: 'trade_fishing', // Fishing
762: 'spell_nature_swiftness', // Riding
};
15 changes: 15 additions & 0 deletions src/app/shared/modules/icon/icon.component.spec.ts
Expand Up @@ -7,19 +7,22 @@ import { PageObject } from '@keira-testing/page-object';
import { IconService } from '@keira-shared/modules/icon/icon.service';
import { of } from 'rxjs';
import Spy = jasmine.Spy;
import { ICON_SKILLS } from '@keira-shared/constants/quest-preview';

@Component({
template: `<keira-icon
[size]="size"
[itemId]="itemId"
[itemDisplayId]="itemDisplayId"
[skillId]="skillId"
></keira-icon>`
})
class TestHostComponent {
@ViewChild(IconComponent) child: IconComponent;
size: string;
itemId: string;
itemDisplayId: string;
skillId: string;
}

class IconComponentPage extends PageObject<TestHostComponent> {
Expand Down Expand Up @@ -71,6 +74,18 @@ describe('ItemIconComponent', () => {
expect(page.img.src).toEqual('https://wow.zamimg.com/images/wow/icons/large/getIconByItemDisplayId-5678.jpg');
});

it('skillId', () => {
const { page, host } = setup();
const skillId = 755; // Jewelcrafting
host.size = 'large';
host.skillId = String(skillId);

page.detectChanges();

expect(page.img.src).toEqual(`https://wow.zamimg.com/images/wow/icons/large/${ICON_SKILLS[skillId]}.jpg`);
});


it('empty', () => {
const { page, host, service } = setup();
host.size = 'medium';
Expand Down
6 changes: 6 additions & 0 deletions src/app/shared/modules/icon/icon.component.ts
@@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
import { IconService } from '@keira-shared/modules/icon/icon.service';
import { SubscriptionHandler } from '@keira-shared/utils/subscription-handler/subscription-handler';
import { ICON_SKILLS } from '@keira-shared/constants/quest-preview';

@Component({
selector: 'keira-icon',
Expand All @@ -23,6 +24,11 @@ export class IconComponent extends SubscriptionHandler {
this.subscriptions.push(this.service.getIconByItemDisplayId(displayId).subscribe(this.setIcon.bind(this)));
}
}
@Input() set skillId(skillId: string | number) {
if (!!skillId && !!ICON_SKILLS[skillId]) {
this.setIcon(ICON_SKILLS[skillId]);
}
}

get iconLink(): string {
return `https://wow.zamimg.com/images/wow/icons/${this.size}/${this._iconId}.jpg`;
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/services/query.service.ts
Expand Up @@ -16,6 +16,7 @@ export abstract class QueryService {
return this.query(query).pipe(
map(data => data && data[0] ? data[0].v as T : null),
);

}

queryValueToPromise<T extends string | number>(query: string): Promise<T> {
Expand Down

0 comments on commit 4d6985a

Please sign in to comment.