diff --git a/src/app/components/editors/gameobject/gameobject-template/gameobject-template.component.html b/src/app/components/editors/gameobject/gameobject-template/gameobject-template.component.html
index 0def2cee3d5..3b03ef51c07 100644
--- a/src/app/components/editors/gameobject/gameobject-template/gameobject-template.component.html
+++ b/src/app/components/editors/gameobject/gameobject-template/gameobject-template.component.html
@@ -84,7 +84,13 @@
- Gameobject Data
+
+ Gameobject Data
+
+
diff --git a/src/app/components/editors/item/item-template/item-template.component.ts b/src/app/components/editors/item/item-template/item-template.component.ts
index 7e9b7cb52c3..c06cf5e1d16 100644
--- a/src/app/components/editors/item/item-template/item-template.component.ts
+++ b/src/app/components/editors/item/item-template/item-template.component.ts
@@ -4,6 +4,26 @@ import { SingleRowEditorComponent } from '../../shared/single-row-editor.compone
import { ItemTemplate } from '../../../../types/item-template.type';
import { ItemTemplateService } from '../../../../services/editors/item/item-template.service';
import { ItemHandlerService } from '../../../../services/handlers/item-handler.service';
+import { ITEM_CLASS, ITEM_SUBCLASS } from '../../../../constants/options/item-class';
+import { ITEM_QUALITY } from '../../../../constants/options/item-quality';
+import { ITEM_FLAGS } from '../../../../constants/flags/item-flags';
+import { ITEM_FLAGS_EXTRA } from '../../../../constants/flags/item-flags-extra';
+import { INVENTORY_TYPE } from '../../../../constants/options/inventory-type';
+import { ALLOWABLE_CLASSES } from '../../../../constants/flags/allowable-classes';
+import { ALLOWABLE_RACES } from '../../../../constants/flags/allowable-races';
+import { FACTION_RANK } from '../../../../constants/options/faction-rank';
+import { BAG_FAMILY } from '../../../../constants/flags/bag-family';
+import { SOCKET_COLOR } from '../../../../constants/flags/socket-color';
+import { ITEM_BONDING } from '../../../../constants/options/item-bonding';
+import { ITEM_MATERIAL } from '../../../../constants/options/item-material';
+import { ITEM_SHEAT } from '../../../../constants/options/item-sheath';
+import { TOTEM_CATEGORY } from '../../../../constants/options/totem-category';
+import { FOOD_TYPE } from '../../../../constants/options/foot-type';
+import { ITEM_FLAGS_CUSTOM } from '../../../../constants/flags/item-flags-custom';
+import { DAMAGE_TYPE } from '../../../../constants/options/damage-type';
+import { SOCKET_BONUS } from '../../../../constants/options/socket-bonus';
+import { FACTIONS } from '../../../../constants/options/faction';
+import { STAT_TYPE } from '../../../../constants/options/stat-type';
@Component({
selector: 'app-item-template',
@@ -12,6 +32,28 @@ import { ItemHandlerService } from '../../../../services/handlers/item-handler.s
})
export class ItemTemplateComponent extends SingleRowEditorComponent
{
+ public readonly ITEM_CLASS = ITEM_CLASS;
+ public readonly ITEM_SUBCLASS = ITEM_SUBCLASS;
+ public readonly ITEM_QUALITY = ITEM_QUALITY;
+ public readonly ITEM_FLAGS = ITEM_FLAGS;
+ public readonly ITEM_FLAGS_EXTRA = ITEM_FLAGS_EXTRA;
+ public readonly INVENTORY_TYPE = INVENTORY_TYPE;
+ public readonly ALLOWABLE_CLASSES = ALLOWABLE_CLASSES;
+ public readonly ALLOWABLE_RACES = ALLOWABLE_RACES;
+ public readonly FACTION_RANK = FACTION_RANK;
+ public readonly BAG_FAMILY = BAG_FAMILY;
+ public readonly SOCKET_COLOR = SOCKET_COLOR;
+ public readonly ITEM_BONDING = ITEM_BONDING;
+ public readonly ITEM_MATERIAL = ITEM_MATERIAL;
+ public readonly ITEM_SHEAT = ITEM_SHEAT;
+ public readonly TOTEM_CATEGORY = TOTEM_CATEGORY;
+ public readonly FOOD_TYPE = FOOD_TYPE;
+ public readonly ITEM_FLAGS_CUSTOM = ITEM_FLAGS_CUSTOM;
+ public readonly DAMAGE_TYPE = DAMAGE_TYPE;
+ public readonly SOCKET_BONUS = SOCKET_BONUS;
+ public readonly FACTIONS = FACTIONS;
+ public readonly STAT_TYPE = STAT_TYPE;
+
/* istanbul ignore next */ // because of: https://github.com/gotwarlost/istanbul/issues/690
constructor(
public editorService: ItemTemplateService,
diff --git a/src/app/components/editors/item/item-template/item-template.integration.spec.ts b/src/app/components/editors/item/item-template/item-template.integration.spec.ts
index e69de29bb2d..e7616b41d73 100644
--- a/src/app/components/editors/item/item-template/item-template.integration.spec.ts
+++ b/src/app/components/editors/item/item-template/item-template.integration.spec.ts
@@ -0,0 +1,214 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { of } from 'rxjs';
+import Spy = jasmine.Spy;
+
+import { ItemTemplateComponent } from './item-template.component';
+import { ItemTemplateModule } from './item-template.module';
+import { QueryService } from '../../../../services/query.service';
+import { EditorPageObject } from '../../../../test-utils/editor-page-object';
+import { ItemTemplate } from '../../../../types/item-template.type';
+import { ItemHandlerService } from '../../../../services/handlers/item-handler.service';
+import { ITEM_SUBCLASS } from '../../../../constants/options/item-class';
+
+class ItemTemplatePage extends EditorPageObject {}
+
+describe('ItemTemplate integration tests', () => {
+ let component: ItemTemplateComponent;
+ let fixture: ComponentFixture;
+ let queryService: QueryService;
+ let querySpy: Spy;
+ let handlerService: ItemHandlerService;
+ let page: ItemTemplatePage;
+
+ const id = 1234;
+ const expectedFullCreateQuery = 'DELETE FROM `item_template` WHERE (`entry` = 1234);\n' +
+ 'INSERT INTO `item_template` (`entry`, `class`, `subclass`, `SoundOverrideSubclass`, `name`, `displayid`, `Quality`, ' +
+ '`Flags`, `FlagsExtra`, `BuyCount`, `BuyPrice`, `SellPrice`, `InventoryType`, `AllowableClass`, `AllowableRace`, ' +
+ '`ItemLevel`, `RequiredLevel`, `RequiredSkill`, `RequiredSkillRank`, `requiredspell`, `requiredhonorrank`, ' +
+ '`RequiredCityRank`, `RequiredReputationFaction`, `RequiredReputationRank`, `maxcount`, `stackable`, `ContainerSlots`, ' +
+ '`StatsCount`, `stat_type1`, `stat_value1`, `stat_type2`, `stat_value2`, `stat_type3`, `stat_value3`, ' +
+ '`stat_type4`, `stat_value4`, `stat_type5`, `stat_value5`, `stat_type6`, `stat_value6`, `stat_type7`, `stat_value7`, ' +
+ '`stat_type8`, `stat_value8`, `stat_type9`, `stat_value9`, `stat_type10`, `stat_value10`, `ScalingStatDistribution`, ' +
+ '`ScalingStatValue`, `dmg_min1`, `dmg_max1`, `dmg_type1`, `dmg_min2`, `dmg_max2`, `dmg_type2`, `armor`, `holy_res`, ' +
+ '`fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `delay`, `ammo_type`, `RangedModRange`, `spellid_1`, ' +
+ '`spelltrigger_1`, `spellcharges_1`, `spellppmRate_1`, `spellcooldown_1`, `spellcategory_1`, `spellcategorycooldown_1`, ' +
+ '`spellid_2`, `spelltrigger_2`, `spellcharges_2`, `spellppmRate_2`, `spellcooldown_2`, `spellcategory_2`, ' +
+ '`spellcategorycooldown_2`, `spellid_3`, `spelltrigger_3`, `spellcharges_3`, `spellppmRate_3`, `spellcooldown_3`, ' +
+ '`spellcategory_3`, `spellcategorycooldown_3`, `spellid_4`, `spelltrigger_4`, `spellcharges_4`, `spellppmRate_4`, ' +
+ '`spellcooldown_4`, `spellcategory_4`, `spellcategorycooldown_4`, `spellid_5`, `spelltrigger_5`, `spellcharges_5`, ' +
+ '`spellppmRate_5`, `spellcooldown_5`, `spellcategory_5`, `spellcategorycooldown_5`, `bonding`, `description`, `PageText`, ' +
+ '`LanguageID`, `PageMaterial`, `startquest`, `lockid`, `Material`, `sheath`, `RandomProperty`, `RandomSuffix`, `block`, ' +
+ '`itemset`, `MaxDurability`, `area`, `Map`, `BagFamily`, `TotemCategory`, `socketColor_1`, `socketContent_1`, `socketColor_2`, ' +
+ '`socketContent_2`, `socketColor_3`, `socketContent_3`, `socketBonus`, `GemProperties`, `RequiredDisenchantSkill`, ' +
+ '`ArmorDamageModifier`, `duration`, `ItemLimitCategory`, `HolidayId`, `ScriptName`, `DisenchantID`, `FoodType`, ' +
+ '`minMoneyLoot`, `maxMoneyLoot`, `flagsCustom`, `VerifiedBuild`) VALUES\n' +
+ '(1234, 0, 0, -1, \'\', 0, 0, 0, 0, 1, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ' +
+ '0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, ' +
+ '0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, \'\', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ' +
+ '0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, \'\', 0, 0, 0, 0, 0, 0);\n';
+
+ const originalEntity = new ItemTemplate();
+ originalEntity.entry = id;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ ItemTemplateModule,
+ RouterTestingModule,
+ ],
+ })
+ .compileComponents();
+ }));
+
+ function setup(creatingNew: boolean) {
+ handlerService = TestBed.get(ItemHandlerService);
+ handlerService['_selected'] = `${id}`;
+ handlerService.isNew = creatingNew;
+
+ queryService = TestBed.get(QueryService);
+ querySpy = spyOn(queryService, 'query').and.returnValue(of());
+
+ spyOn(queryService, 'selectAll').and.returnValue(of(
+ { results: creatingNew ? [] : [originalEntity] }
+ ));
+
+ fixture = TestBed.createComponent(ItemTemplateComponent);
+ component = fixture.componentInstance;
+ page = new ItemTemplatePage(fixture);
+ fixture.autoDetectChanges(true);
+ fixture.detectChanges();
+ }
+
+ describe('Creating new', () => {
+ beforeEach(() => setup(true));
+
+ it('should correctly initialise', () => {
+ page.expectQuerySwitchToBeHidden();
+ page.expectFullQueryToBeShown();
+ page.expectFullQueryToContain(expectedFullCreateQuery);
+ });
+
+ it('changing a property and executing the query should correctly work', () => {
+ querySpy.calls.reset();
+
+ page.setInputValueById('name', 'Shin');
+ page.clickExecuteQuery();
+
+ // Note: full query check has been shortened here because the table is too big, don't do this in other tests unless necessary
+ page.expectFullQueryToContain('Shin');
+ expect(querySpy).toHaveBeenCalledTimes(1);
+ expect(querySpy.calls.mostRecent().args[0]).toContain('Shin');
+ });
+ });
+
+ describe('Editing existing', () => {
+ beforeEach(() => setup(false));
+
+ it('should correctly initialise', () => {
+ page.expectDiffQueryToBeShown();
+ page.expectDiffQueryToBeEmpty();
+ page.expectFullQueryToContain(expectedFullCreateQuery);
+ });
+
+ it('changing all properties and executing the query should correctly work', () => {
+ const expectedQuery = 'UPDATE `item_template` SET ' +
+ '`subclass` = 1, `SoundOverrideSubclass` = 2, `name` = \'3\', `displayid` = 4, `Quality` = 5, `Flags` = 6, `FlagsExtra` = 7, ' +
+ '`BuyCount` = 8, `BuyPrice` = 9, `SellPrice` = 10, `InventoryType` = 11, `AllowableClass` = 12, `AllowableRace` = 13, ' +
+ '`ItemLevel` = 14, `RequiredLevel` = 15, `RequiredSkill` = 16, `RequiredSkillRank` = 17, `requiredspell` = 18, ' +
+ '`requiredhonorrank` = 19, `RequiredCityRank` = 20, `RequiredReputationFaction` = 21, `RequiredReputationRank` = 22, ' +
+ '`maxcount` = 23, `stackable` = 24, `ContainerSlots` = 25, `StatsCount` = 26, `stat_type1` = 27, `stat_value1` = 28, ' +
+ '`stat_type2` = 29, `stat_value2` = 30, `stat_type3` = 31, `stat_value3` = 32, `stat_type4` = 33, `stat_value4` = 34, ' +
+ '`stat_type5` = 35, `stat_value5` = 36, `stat_type6` = 37, `stat_value6` = 38, `stat_type7` = 39, `stat_value7` = 40, ' +
+ '`stat_type8` = 41, `stat_value8` = 42, `stat_type9` = 43, `stat_value9` = 44, `stat_type10` = 45, `stat_value10` = 46, ' +
+ '`ScalingStatDistribution` = 47, `ScalingStatValue` = 48, `dmg_min1` = 49, `dmg_max1` = 50, `dmg_type1` = 51, ' +
+ '`dmg_min2` = 52, `dmg_max2` = 53, `dmg_type2` = 54, `armor` = 55, `holy_res` = 56, `fire_res` = 57, `nature_res` = 58, ' +
+ '`frost_res` = 59, `shadow_res` = 60, `arcane_res` = 61, `delay` = 62, `ammo_type` = 63, `RangedModRange` = 64, ' +
+ '`spellid_1` = 65, `spelltrigger_1` = 66, `spellcharges_1` = 67, `spellppmRate_1` = 68, `spellcooldown_1` = 69, ' +
+ '`spellcategory_1` = 70, `spellcategorycooldown_1` = 71, `spellid_2` = 72, `spelltrigger_2` = 73, `spellcharges_2` = 74, ' +
+ '`spellppmRate_2` = 75, `spellcooldown_2` = 76, `spellcategory_2` = 77, `spellcategorycooldown_2` = 78, `spellid_3` = 79, ' +
+ '`spelltrigger_3` = 80, `spellcharges_3` = 81, `spellppmRate_3` = 82, `spellcooldown_3` = 83, `spellcategory_3` = 84, ' +
+ '`spellcategorycooldown_3` = 85, `spellid_4` = 86, `spelltrigger_4` = 87, `spellcharges_4` = 88, `spellppmRate_4` = 89, ' +
+ '`spellcooldown_4` = 90, `spellcategory_4` = 91, `spellcategorycooldown_4` = 92, `spellid_5` = 93, `spelltrigger_5` = 94, ' +
+ '`spellcharges_5` = 95, `spellppmRate_5` = 96, `spellcooldown_5` = 97, `spellcategory_5` = 98, ' +
+ '`spellcategorycooldown_5` = 99, `bonding` = 100, `description` = \'101\', `PageText` = 102, `LanguageID` = 103, ' +
+ '`PageMaterial` = 104, `startquest` = 105, `lockid` = 106, `Material` = 107, `sheath` = 108, `RandomProperty` = 109, ' +
+ '`RandomSuffix` = 110, `block` = 111, `itemset` = 112, `MaxDurability` = 113, `area` = 114, `Map` = 115, `BagFamily` = 116, ' +
+ '`TotemCategory` = 117, `socketColor_1` = 118, `socketContent_1` = 119, `socketColor_2` = 120, `socketContent_2` = 121, ' +
+ '`socketColor_3` = 122, `socketContent_3` = 123, `socketBonus` = 124, `GemProperties` = 125, `RequiredDisenchantSkill` = 126, ' +
+ '`ArmorDamageModifier` = 127, `duration` = 128, `ItemLimitCategory` = 129, `HolidayId` = 130, `ScriptName` = \'131\', ' +
+ '`DisenchantID` = 132, `FoodType` = 133, `minMoneyLoot` = 134, `maxMoneyLoot` = 135, `flagsCustom` = 136 WHERE (`entry` = 1234);';
+ querySpy.calls.reset();
+
+ page.changeAllFields(originalEntity, ['VerifiedBuild']);
+ page.clickExecuteQuery();
+
+ page.expectDiffQueryToContain(expectedQuery);
+ expect(querySpy).toHaveBeenCalledTimes(1);
+ expect(querySpy.calls.mostRecent().args[0]).toContain(expectedQuery);
+ });
+
+ it('changing values should correctly update the queries', () => {
+ // Note: full query check has been shortened here because the table is too big, don't do this in other tests unless necessary
+
+ page.setInputValueById('name', 'Shin');
+ page.expectDiffQueryToContain(
+ 'UPDATE `item_template` SET `name` = \'Shin\' WHERE (`entry` = 1234);'
+ );
+ page.expectFullQueryToContain('Shin');
+
+ page.setInputValueById('BuyCount', 22);
+ page.expectDiffQueryToContain(
+ 'UPDATE `item_template` SET `name` = \'Shin\', `BuyCount` = 22 WHERE (`entry` = 1234);'
+ );
+ page.expectFullQueryToContain('Shin');
+ page.expectFullQueryToContain('22');
+ });
+
+ it('changing a value via FlagsSelector should correctly work', () => {
+ const field = 'Flags';
+ page.clickElement(page.getSelectorBtn(field));
+ page.expectModalDisplayed();
+
+ page.toggleFlagInRow(2);
+ page.toggleFlagInRow(12);
+ page.clickModalSelect();
+
+ expect(page.getInputById(field).value).toEqual('4100');
+ page.expectDiffQueryToContain(
+ 'UPDATE `item_template` SET `Flags` = 4100 WHERE (`entry` = 1234);'
+ );
+
+ // Note: full query check has been shortened here because the table is too big, don't do this in other tests unless necessary
+ page.expectFullQueryToContain('4100');
+ });
+
+ describe('the subclass field', () => {
+ it('should show the selector button only if class has a valid value', () => {
+ page.setInputValueById('class', 100);
+ expect(page.getSelectorBtn('subclass', false)).toBeFalsy();
+
+ page.setInputValueById('class', 0);
+ expect(page.getSelectorBtn('subclass', false)).toBeTruthy();
+
+ page.setInputValueById('class', -1);
+ expect(page.getSelectorBtn('subclass', false)).toBeFalsy();
+
+ page.setInputValueById('class', 10);
+ expect(page.getSelectorBtn('subclass', false)).toBeTruthy();
+
+ page.setInputValueById('class', null);
+ expect(page.getSelectorBtn('subclass', false)).toBeFalsy();
+ });
+
+ it('should show its values according to the value of class', () => {
+ page.setInputValueById('class', 3);
+ page.clickElement(page.getSelectorBtn('subclass'));
+
+ expect(page.getCellOfDatatableInModal(2, 1).innerText).toContain(ITEM_SUBCLASS[3][2].name);
+ page.clickModalSelect();
+ });
+ });
+ });
+});
+
diff --git a/src/app/components/editors/item/item-template/item-template.module.ts b/src/app/components/editors/item/item-template/item-template.module.ts
index aeffa269e07..f12affafe7c 100644
--- a/src/app/components/editors/item/item-template/item-template.module.ts
+++ b/src/app/components/editors/item/item-template/item-template.module.ts
@@ -8,7 +8,6 @@ import { QueryOutputModule } from '../../shared/query-output/query-output.module
import { ItemTemplateComponent } from './item-template.component';
import { SingleValueSelectorModule } from '../../shared/selectors/single-value-selector/single-value-selector.module';
import { FlagsSelectorModule } from '../../shared/selectors/flags-selector/flags-selector.module';
-import { ItemSelectorModule } from '../../shared/selectors/item-selector/item-selector.module';
@NgModule({
declarations: [
@@ -22,7 +21,6 @@ import { ItemSelectorModule } from '../../shared/selectors/item-selector/item-se
TooltipModule.forRoot(),
SingleValueSelectorModule,
FlagsSelectorModule,
- ItemSelectorModule,
],
exports: [
ItemTemplateComponent,
diff --git a/src/app/components/editors/item/item.module.ts b/src/app/components/editors/item/item.module.ts
index 5d86b6a28e9..fec9ac9887f 100644
--- a/src/app/components/editors/item/item.module.ts
+++ b/src/app/components/editors/item/item.module.ts
@@ -1,10 +1,20 @@
import { NgModule } from '@angular/core';
import { ItemTemplateModule } from './item-template/item-template.module';
import { SelectItemModule } from './select-item/select-item.module';
+import { ItemLootTemplateModule } from './item-loot-template/item-loot-template.module';
+import { DisenchantLootTemplateModule } from './disenchant-loot-template/disenchant-loot-template.module';
+import { ProspectingLootTemplateModule } from './prospecting-loot-template/prospecting-loot-template.module';
+import { MillingLootTemplateModule } from './milling-loot-template/milling-loot-template.module';
+import { ItemEnchantmentTemplateModule } from './item-enchantment/item-enchantment-template.module';
const modules = [
SelectItemModule,
ItemTemplateModule,
+ ItemEnchantmentTemplateModule,
+ ItemLootTemplateModule,
+ DisenchantLootTemplateModule,
+ ProspectingLootTemplateModule,
+ MillingLootTemplateModule
];
@NgModule({
diff --git a/src/app/components/editors/item/milling-loot-template/milling-loot-template.component.ts b/src/app/components/editors/item/milling-loot-template/milling-loot-template.component.ts
new file mode 100644
index 00000000000..79229e8f9eb
--- /dev/null
+++ b/src/app/components/editors/item/milling-loot-template/milling-loot-template.component.ts
@@ -0,0 +1,25 @@
+import { Component } from '@angular/core';
+
+import { ItemHandlerService } from '../../../../services/handlers/item-handler.service';
+import { MillingLootTemplate } from '../../../../types/milling-loot-template.type';
+import { MillingLootTemplateService } from '../../../../services/editors/item/milling-loot-template.service';
+import { MultiRowEditorComponent } from '../../shared/multi-row-editor.component';
+import { LOOT_MODE } from '../../../../constants/flags/loot-mode';
+
+@Component({
+ selector: 'app-milling-loot-template',
+ templateUrl: '../item-loot-template/item-loot-template.component.html',
+ styleUrls: ['../item-loot-template/item-loot-template.component.scss']
+})
+export class MillingLootTemplateComponent extends MultiRowEditorComponent {
+
+ public readonly LOOT_MODE = LOOT_MODE;
+
+ /* istanbul ignore next */ // because of: https://github.com/gotwarlost/istanbul/issues/690
+ constructor(
+ public editorService: MillingLootTemplateService,
+ public handlerService: ItemHandlerService,
+ ) {
+ super(editorService, handlerService);
+ }
+}
diff --git a/src/app/components/editors/item/milling-loot-template/milling-loot-template.integration.spec.ts b/src/app/components/editors/item/milling-loot-template/milling-loot-template.integration.spec.ts
new file mode 100644
index 00000000000..de6ce21c28c
--- /dev/null
+++ b/src/app/components/editors/item/milling-loot-template/milling-loot-template.integration.spec.ts
@@ -0,0 +1,274 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { of } from 'rxjs';
+import Spy = jasmine.Spy;
+
+import { MillingLootTemplateComponent } from './milling-loot-template.component';
+import { MillingLootTemplateModule } from './milling-loot-template.module';
+import { QueryService } from '../../../../services/query.service';
+import { MillingLootTemplate } from '../../../../types/milling-loot-template.type';
+import { ItemHandlerService } from '../../../../services/handlers/item-handler.service';
+import { MultiRowEditorPageObject } from '../../../../test-utils/multi-row-editor-page-object';
+
+class MillingLootTemplatePage extends MultiRowEditorPageObject {}
+
+describe('MillingLootTemplate integration tests', () => {
+ let component: MillingLootTemplateComponent;
+ let fixture: ComponentFixture;
+ let queryService: QueryService;
+ let querySpy: Spy;
+ let handlerService: ItemHandlerService;
+ let page: MillingLootTemplatePage;
+
+ const id = 1234;
+
+ const originalRow0 = new MillingLootTemplate();
+ const originalRow1 = new MillingLootTemplate();
+ const originalRow2 = new MillingLootTemplate();
+ originalRow0.Entry = originalRow1.Entry = originalRow2.Entry = id;
+ originalRow0.Item = 0;
+ originalRow1.Item = 1;
+ originalRow2.Item = 2;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ MillingLootTemplateModule,
+ RouterTestingModule,
+ ],
+ })
+ .compileComponents();
+ }));
+
+ function setup(creatingNew: boolean) {
+ handlerService = TestBed.get(ItemHandlerService);
+ handlerService['_selected'] = `${id}`;
+ handlerService.isNew = creatingNew;
+
+ queryService = TestBed.get(QueryService);
+ querySpy = spyOn(queryService, 'query').and.returnValue(of());
+
+ spyOn(queryService, 'selectAll').and.returnValue(of(
+ { results: creatingNew ? [] : [originalRow0, originalRow1, originalRow2] }
+ ));
+
+ fixture = TestBed.createComponent(MillingLootTemplateComponent);
+ component = fixture.componentInstance;
+ page = new MillingLootTemplatePage(fixture);
+ fixture.autoDetectChanges(true);
+ fixture.detectChanges();
+ }
+
+ describe('Creating new', () => {
+ beforeEach(() => setup(true));
+
+ it('should correctly initialise', () => {
+ page.expectDiffQueryToBeEmpty();
+ page.expectFullQueryToBeEmpty();
+ expect(page.formError.hidden).toBe(true);
+ expect(page.addNewRowBtn.disabled).toBe(false);
+ expect(page.deleteSelectedRowBtn.disabled).toBe(true);
+ expect(page.getInputById('Item').disabled).toBe(true);
+ expect(page.getInputById('Reference').disabled).toBe(true);
+ expect(page.getInputById('Chance').disabled).toBe(true);
+ expect(page.getInputById('QuestRequired').disabled).toBe(true);
+ expect(page.getInputById('LootMode').disabled).toBe(true);
+ expect(page.getInputById('GroupId').disabled).toBe(true);
+ expect(page.getInputById('MinCount').disabled).toBe(true);
+ expect(page.getInputById('MaxCount').disabled).toBe(true);
+ expect(page.getInputById('Comment').disabled).toBe(true);
+ expect(page.getEditorTableRowsCount()).toBe(0);
+ });
+
+ it('adding new rows and executing the query should correctly work', () => {
+ const expectedQuery = 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0, 1, 2));\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 0, 1, 1, \'\');';
+ querySpy.calls.reset();
+
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(1);
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(2);
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(3);
+ page.clickExecuteQuery();
+
+ page.expectDiffQueryToContain(expectedQuery);
+ expect(querySpy).toHaveBeenCalledTimes(1);
+ expect(querySpy.calls.mostRecent().args[0]).toContain(expectedQuery);
+ });
+
+ it('adding a row and changing its values should correctly update the queries', () => {
+ page.addNewRow();
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0));\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.setInputValueById('Chance', '1');
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0));\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 0, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.setInputValueById('QuestRequired', '2');
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0));\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+
+ page.setInputValueById('Item', '123');
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (123));\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 123, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 123, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+ });
+ });
+
+ describe('Editing existing', () => {
+ beforeEach(() => setup(false));
+
+ it('should correctly initialise', () => {
+ expect(page.formError.hidden).toBe(true);
+ page.expectDiffQueryToBeShown();
+ page.expectDiffQueryToBeEmpty();
+ page.expectFullQueryToContain('' +
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 0, 1, 1, \'\');');
+ expect(page.getEditorTableRowsCount()).toBe(3);
+ });
+
+ it('deleting rows should correctly work', () => {
+ page.deleteRow(1);
+ expect(page.getEditorTableRowsCount()).toBe(2);
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (1));'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.deleteRow(1);
+ expect(page.getEditorTableRowsCount()).toBe(1);
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (1, 2));'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.deleteRow(0);
+ expect(page.getEditorTableRowsCount()).toBe(0);
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE `Entry` = 1234;'
+ );
+ page.expectFullQueryToBeEmpty();
+ });
+
+ it('editing existing rows should correctly work', () => {
+ page.clickRowOfDatatable(1);
+ page.setInputValueById('LootMode', 1);
+
+ page.clickRowOfDatatable(2);
+ page.setInputValueById('GroupId', 2);
+
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (2));\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 2, 0, 100, 0, 1, 2, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 2, 1, 1, \'\');'
+ );
+ });
+
+ it('combining add, edit and delete should correctly work', () => {
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(4);
+
+ page.clickRowOfDatatable(1);
+ page.setInputValueById('Chance', 10);
+ expect(page.getEditorTableRowsCount()).toBe(4);
+
+ page.deleteRow(2);
+ expect(page.getEditorTableRowsCount()).toBe(3);
+
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (1, 2, 3));\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 1, 0, 10, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 3, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `milling_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `milling_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 10, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 3, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+ });
+
+ it('using the same row id for multiple rows should correctly show an error', () => {
+ page.clickRowOfDatatable(2);
+ page.setInputValueById('Item', 0);
+
+ page.expectUniqueError();
+ });
+ });
+
+});
diff --git a/src/app/components/editors/item/milling-loot-template/milling-loot-template.module.ts b/src/app/components/editors/item/milling-loot-template/milling-loot-template.module.ts
new file mode 100644
index 00000000000..7fa74132922
--- /dev/null
+++ b/src/app/components/editors/item/milling-loot-template/milling-loot-template.module.ts
@@ -0,0 +1,31 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { ReactiveFormsModule } from '@angular/forms';
+
+import { TopBarModule } from '../../shared/top-bar/top-bar.module';
+import { QueryOutputModule } from '../../shared/query-output/query-output.module';
+import { MillingLootTemplateComponent } from './milling-loot-template.component';
+import { ItemSelectorModule } from '../../shared/selectors/item-selector/item-selector.module';
+import { TooltipModule } from 'ngx-bootstrap';
+import { FlagsSelectorModule } from '../../shared/selectors/flags-selector/flags-selector.module';
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+
+@NgModule({
+ declarations: [
+ MillingLootTemplateComponent,
+ ],
+ imports: [
+ BrowserModule,
+ ReactiveFormsModule,
+ TopBarModule,
+ QueryOutputModule,
+ TooltipModule.forRoot(),
+ ItemSelectorModule,
+ FlagsSelectorModule,
+ NgxDatatableModule,
+ ],
+ exports: [
+ MillingLootTemplateComponent,
+ ],
+})
+export class MillingLootTemplateModule {}
diff --git a/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.component.ts b/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.component.ts
new file mode 100644
index 00000000000..f29b38cb17a
--- /dev/null
+++ b/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.component.ts
@@ -0,0 +1,25 @@
+import { Component } from '@angular/core';
+
+import { ItemHandlerService } from '../../../../services/handlers/item-handler.service';
+import { ProspectingLootTemplate } from '../../../../types/prospecting-loot-template.type';
+import { ProspectingLootTemplateService } from '../../../../services/editors/item/prospecting-loot-template.service';
+import { MultiRowEditorComponent } from '../../shared/multi-row-editor.component';
+import { LOOT_MODE } from '../../../../constants/flags/loot-mode';
+
+@Component({
+ selector: 'app-prospecting-loot-template',
+ templateUrl: '../item-loot-template/item-loot-template.component.html',
+ styleUrls: ['../item-loot-template/item-loot-template.component.scss']
+})
+export class ProspectingLootTemplateComponent extends MultiRowEditorComponent {
+
+ public readonly LOOT_MODE = LOOT_MODE;
+
+ /* istanbul ignore next */ // because of: https://github.com/gotwarlost/istanbul/issues/690
+ constructor(
+ public editorService: ProspectingLootTemplateService,
+ public handlerService: ItemHandlerService,
+ ) {
+ super(editorService, handlerService);
+ }
+}
diff --git a/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.integration.spec.ts b/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.integration.spec.ts
new file mode 100644
index 00000000000..2c8a524ee1c
--- /dev/null
+++ b/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.integration.spec.ts
@@ -0,0 +1,274 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { of } from 'rxjs';
+import Spy = jasmine.Spy;
+
+import { ProspectingLootTemplateComponent } from './prospecting-loot-template.component';
+import { ProspectingLootTemplateModule } from './prospecting-loot-template.module';
+import { QueryService } from '../../../../services/query.service';
+import { ProspectingLootTemplate } from '../../../../types/prospecting-loot-template.type';
+import { ItemHandlerService } from '../../../../services/handlers/item-handler.service';
+import { MultiRowEditorPageObject } from '../../../../test-utils/multi-row-editor-page-object';
+
+class ProspectingLootTemplatePage extends MultiRowEditorPageObject {}
+
+describe('ProspectingLootTemplate integration tests', () => {
+ let component: ProspectingLootTemplateComponent;
+ let fixture: ComponentFixture;
+ let queryService: QueryService;
+ let querySpy: Spy;
+ let handlerService: ItemHandlerService;
+ let page: ProspectingLootTemplatePage;
+
+ const id = 1234;
+
+ const originalRow0 = new ProspectingLootTemplate();
+ const originalRow1 = new ProspectingLootTemplate();
+ const originalRow2 = new ProspectingLootTemplate();
+ originalRow0.Entry = originalRow1.Entry = originalRow2.Entry = id;
+ originalRow0.Item = 0;
+ originalRow1.Item = 1;
+ originalRow2.Item = 2;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ ProspectingLootTemplateModule,
+ RouterTestingModule,
+ ],
+ })
+ .compileComponents();
+ }));
+
+ function setup(creatingNew: boolean) {
+ handlerService = TestBed.get(ItemHandlerService);
+ handlerService['_selected'] = `${id}`;
+ handlerService.isNew = creatingNew;
+
+ queryService = TestBed.get(QueryService);
+ querySpy = spyOn(queryService, 'query').and.returnValue(of());
+
+ spyOn(queryService, 'selectAll').and.returnValue(of(
+ { results: creatingNew ? [] : [originalRow0, originalRow1, originalRow2] }
+ ));
+
+ fixture = TestBed.createComponent(ProspectingLootTemplateComponent);
+ component = fixture.componentInstance;
+ page = new ProspectingLootTemplatePage(fixture);
+ fixture.autoDetectChanges(true);
+ fixture.detectChanges();
+ }
+
+ describe('Creating new', () => {
+ beforeEach(() => setup(true));
+
+ it('should correctly initialise', () => {
+ page.expectDiffQueryToBeEmpty();
+ page.expectFullQueryToBeEmpty();
+ expect(page.formError.hidden).toBe(true);
+ expect(page.addNewRowBtn.disabled).toBe(false);
+ expect(page.deleteSelectedRowBtn.disabled).toBe(true);
+ expect(page.getInputById('Item').disabled).toBe(true);
+ expect(page.getInputById('Reference').disabled).toBe(true);
+ expect(page.getInputById('Chance').disabled).toBe(true);
+ expect(page.getInputById('QuestRequired').disabled).toBe(true);
+ expect(page.getInputById('LootMode').disabled).toBe(true);
+ expect(page.getInputById('GroupId').disabled).toBe(true);
+ expect(page.getInputById('MinCount').disabled).toBe(true);
+ expect(page.getInputById('MaxCount').disabled).toBe(true);
+ expect(page.getInputById('Comment').disabled).toBe(true);
+ expect(page.getEditorTableRowsCount()).toBe(0);
+ });
+
+ it('adding new rows and executing the query should correctly work', () => {
+ const expectedQuery = 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0, 1, 2));\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 0, 1, 1, \'\');';
+ querySpy.calls.reset();
+
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(1);
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(2);
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(3);
+ page.clickExecuteQuery();
+
+ page.expectDiffQueryToContain(expectedQuery);
+ expect(querySpy).toHaveBeenCalledTimes(1);
+ expect(querySpy.calls.mostRecent().args[0]).toContain(expectedQuery);
+ });
+
+ it('adding a row and changing its values should correctly update the queries', () => {
+ page.addNewRow();
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0));\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.setInputValueById('Chance', '1');
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0));\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 0, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.setInputValueById('QuestRequired', '2');
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (0));\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+
+ page.setInputValueById('Item', '123');
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (123));\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 123, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 123, 0, 1, 2, 1, 0, 1, 1, \'\');'
+ );
+ });
+ });
+
+ describe('Editing existing', () => {
+ beforeEach(() => setup(false));
+
+ it('should correctly initialise', () => {
+ expect(page.formError.hidden).toBe(true);
+ page.expectDiffQueryToBeShown();
+ page.expectDiffQueryToBeEmpty();
+ page.expectFullQueryToContain('' +
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, ' +
+ '`GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 0, 1, 1, \'\');');
+ expect(page.getEditorTableRowsCount()).toBe(3);
+ });
+
+ it('deleting rows should correctly work', () => {
+ page.deleteRow(1);
+ expect(page.getEditorTableRowsCount()).toBe(2);
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (1));'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.deleteRow(1);
+ expect(page.getEditorTableRowsCount()).toBe(1);
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (1, 2));'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+
+ page.deleteRow(0);
+ expect(page.getEditorTableRowsCount()).toBe(0);
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE `Entry` = 1234;'
+ );
+ page.expectFullQueryToBeEmpty();
+ });
+
+ it('editing existing rows should correctly work', () => {
+ page.clickRowOfDatatable(1);
+ page.setInputValueById('LootMode', 1);
+
+ page.clickRowOfDatatable(2);
+ page.setInputValueById('GroupId', 2);
+
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (2));\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 2, 0, 100, 0, 1, 2, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 2, 0, 100, 0, 1, 2, 1, 1, \'\');'
+ );
+ });
+
+ it('combining add, edit and delete should correctly work', () => {
+ page.addNewRow();
+ expect(page.getEditorTableRowsCount()).toBe(4);
+
+ page.clickRowOfDatatable(1);
+ page.setInputValueById('Chance', 10);
+ expect(page.getEditorTableRowsCount()).toBe(4);
+
+ page.deleteRow(2);
+ expect(page.getEditorTableRowsCount()).toBe(3);
+
+ page.expectDiffQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234) AND (`Item` IN (1, 2, 3));\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 1, 0, 10, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 3, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+ page.expectFullQueryToContain(
+ 'DELETE FROM `prospecting_loot_template` WHERE (`Entry` = 1234);\n' +
+ 'INSERT INTO `prospecting_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, ' +
+ '`MinCount`, `MaxCount`, `Comment`) VALUES\n' +
+ '(1234, 0, 0, 100, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 1, 0, 10, 0, 1, 0, 1, 1, \'\'),\n' +
+ '(1234, 3, 0, 100, 0, 1, 0, 1, 1, \'\');'
+ );
+ });
+
+ it('using the same row id for multiple rows should correctly show an error', () => {
+ page.clickRowOfDatatable(2);
+ page.setInputValueById('Item', 0);
+
+ page.expectUniqueError();
+ });
+ });
+
+});
diff --git a/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.module.ts b/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.module.ts
new file mode 100644
index 00000000000..18eaf336dbd
--- /dev/null
+++ b/src/app/components/editors/item/prospecting-loot-template/prospecting-loot-template.module.ts
@@ -0,0 +1,31 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { ReactiveFormsModule } from '@angular/forms';
+
+import { TopBarModule } from '../../shared/top-bar/top-bar.module';
+import { QueryOutputModule } from '../../shared/query-output/query-output.module';
+import { ProspectingLootTemplateComponent } from './prospecting-loot-template.component';
+import { ItemSelectorModule } from '../../shared/selectors/item-selector/item-selector.module';
+import { TooltipModule } from 'ngx-bootstrap';
+import { FlagsSelectorModule } from '../../shared/selectors/flags-selector/flags-selector.module';
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+
+@NgModule({
+ declarations: [
+ ProspectingLootTemplateComponent,
+ ],
+ imports: [
+ BrowserModule,
+ ReactiveFormsModule,
+ TopBarModule,
+ QueryOutputModule,
+ TooltipModule.forRoot(),
+ ItemSelectorModule,
+ FlagsSelectorModule,
+ NgxDatatableModule,
+ ],
+ exports: [
+ ProspectingLootTemplateComponent,
+ ],
+})
+export class ProspectingLootTemplateModule {}
diff --git a/src/app/components/editors/item/select-item/select-item.component.html b/src/app/components/editors/item/select-item/select-item.component.html
index 0cfbcd7ad59..adbc9853222 100644
--- a/src/app/components/editors/item/select-item/select-item.component.html
+++ b/src/app/components/editors/item/select-item/select-item.component.html
@@ -57,7 +57,13 @@
{{ row.entry }}
-
+
+
+
+
+
+
+
diff --git a/src/app/components/main-window/sidebar/sidebar.component.html b/src/app/components/main-window/sidebar/sidebar.component.html
index 37f9d54b235..d103c008925 100644
--- a/src/app/components/main-window/sidebar/sidebar.component.html
+++ b/src/app/components/main-window/sidebar/sidebar.component.html
@@ -254,7 +254,31 @@