Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

Commit 1d7491d

Browse files
authored
fix: DEV-3888: Fix hotkeys of dynamic children (#1131)
* fix: DEV-3888: Fix hotkeys of dynamic children * test: DEV-3888: Add test for hotkeys of dynamic children
1 parent 5450bc0 commit 1d7491d

File tree

4 files changed

+199
-12
lines changed

4 files changed

+199
-12
lines changed

e2e/codecept.conf.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// HEADLESS=true npx codecept run
33
const headless = process.env.HEADLESS;
44
const port = process.env.LSF_PORT ?? 3000;
5+
const fs = require('fs');
6+
const FRAGMENTS_PATH = './fragments/';
57

68
module.exports.config = {
79
timeout: 60 * 30, // Time out after 30 minutes
@@ -38,16 +40,11 @@ module.exports.config = {
3840
},
3941
include: {
4042
I: './steps_file.js',
41-
LabelStudio: './fragments/LabelStudio.js',
42-
AtImageView: './fragments/AtImageView.js',
43-
AtAudioView: './fragments/AtAudioView.js',
44-
AtRichText: './fragments/AtRichText.js',
45-
AtSidebar: './fragments/AtSidebar.js',
46-
AtLabels: './fragments/AtLabels.js',
47-
AtSettings: './fragments/AtSettings.js',
48-
AtTopbar: './fragments/AtTopbar.js',
49-
AtParagraphs: './fragments/AtParagraphs.js',
50-
ErrorsCollector: './fragments/ErrorsCollector.js',
43+
...(Object.fromEntries(fs.readdirSync(FRAGMENTS_PATH).map(path => {
44+
const name = path.split('.')[0];
45+
46+
return [name, `${FRAGMENTS_PATH}${path}`];
47+
}))),
5148
},
5249
bootstrap: null,
5350
mocha: {
@@ -73,8 +70,12 @@ module.exports.config = {
7370
// For the future generations
7471
// coverage: {
7572
// enabled: true,
76-
// coverageDir: "output/coverage",
73+
// coverageDir: 'output/coverage',
7774
// },
75+
featureFlags: {
76+
require: './plugins/featureFlags.js',
77+
enabled: true,
78+
},
7879
screenshotOnFail: {
7980
enabled: true,
8081
},

e2e/plugins/featureFlags.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
const { recorder, event } = require('codeceptjs');
2+
const Container = require('codeceptjs/lib/container');
3+
4+
const defaultConfig = {
5+
};
6+
7+
const supportedHelpers = ['Playwright'];
8+
9+
/**
10+
* This plugin will listen for setting feature flags and apply them at the moment of page loading.
11+
* In this case set feature flags will affect the whole code including models initialization,
12+
* and other similar parts that will run on the the scripts load.
13+
*/
14+
15+
module.exports = function(config) {
16+
const helpers = Container.helpers();
17+
let helper;
18+
19+
for (const helperName of supportedHelpers) {
20+
if (Object.keys(helpers).indexOf(helperName) > -1) {
21+
helper = helpers[helperName];
22+
}
23+
}
24+
25+
if (!helper) {
26+
console.error('Feature flags is only supported in Playwright');
27+
return;
28+
}
29+
30+
const options = Object.assign(defaultConfig, helper.options, config);
31+
32+
if (options.enable) return;
33+
34+
let defaultValue;
35+
let ffs = {};
36+
37+
function hasStepName(name, step) {
38+
return step && (name === step.name || hasStepName(name, step.metaStep));
39+
}
40+
41+
event.dispatcher.on(event.test.before, async () => {
42+
ffs = {};
43+
});
44+
45+
event.dispatcher.on(event.step.before, async (step) => {
46+
if (hasStepName('amOnPage', step)) {
47+
recorder.add('set feature flags', async () => {
48+
try {
49+
helper.page.once('requestfinished',
50+
request => {
51+
const url = request.url();
52+
53+
if (url.startsWith(options.url + step.args[0])) {
54+
helper.page.evaluate((config) => {
55+
if (!window.APP_SETTINGS) window.APP_SETTINGS = {};
56+
if (!window.APP_SETTINGS.feature_flags) window.APP_SETTINGS.feature_flags = {};
57+
window.APP_SETTINGS.feature_flags = {
58+
...window.APP_SETTINGS.feature_flags,
59+
...config.feature_flags,
60+
};
61+
if (typeof config.feature_flags_default_value === 'boolean') {
62+
window.APP_SETTINGS.feature_flags_default_value = config.feature_flags_default_value;
63+
}
64+
}, { feature_flags: ffs, feature_flags_default_value: defaultValue });
65+
}
66+
},
67+
);
68+
} catch (err) {
69+
console.error(err);
70+
}
71+
});
72+
}
73+
if (hasStepName('setFeatureFlags', step)) {
74+
recorder.add('remember feature flags', async () => {
75+
try {
76+
ffs = {
77+
...ffs,
78+
...step.args[1],
79+
};
80+
} catch (err) {
81+
console.error(err);
82+
}
83+
});
84+
}
85+
if (hasStepName('setFeatureFlagsDefaultValue', step)) {
86+
recorder.add('remember feature flags default value', async () => {
87+
try {
88+
defaultValue = step.args[1];
89+
} catch (err) {
90+
console.error(err);
91+
}
92+
});
93+
}
94+
});
95+
};
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* global Feature, Scenario */
2+
const assert = require('assert');
3+
4+
Feature('Dynamic choices').tag('@regress');
5+
6+
Scenario('Hotkeys for dynamic choices', async ({ I, LabelStudio })=>{
7+
LabelStudio.setFeatureFlags({
8+
ff_dev_2007_rework_choices_280322_short: true,
9+
ff_dev_2007_dev_2008_dynamic_tag_children_250322_short: true,
10+
ff_dev_2100_preselected_choices_250422_short: true,
11+
ff_front_dev_2244_nested_choices_des_107_160522_short: true,
12+
});
13+
14+
const params = {
15+
config: `
16+
<View>
17+
<Text name="text" value="$text"></Text>
18+
<Choices name="choices" toName="text" allownested="true" choice="multiple" value="$choices">
19+
<Choice value="Static option" hotkey="s" />
20+
</Choices>
21+
</View>`,
22+
data: {
23+
text: 'Some text',
24+
choices: [
25+
{
26+
value: 'Header 1',
27+
children: [
28+
{
29+
value: 'Option 1.1',
30+
},
31+
{
32+
value: 'Option 1.2',
33+
},
34+
],
35+
},
36+
{
37+
value: 'Header 2',
38+
children: [
39+
{
40+
value: 'Option 2.1',
41+
},
42+
{
43+
value: 'Option 2.2',
44+
},
45+
{
46+
value: 'Option 2.3',
47+
hotkey: 'q',
48+
},
49+
],
50+
},
51+
],
52+
},
53+
annotations: [{
54+
id: 'test',
55+
result: [],
56+
}],
57+
};
58+
59+
I.amOnPage('/');
60+
61+
LabelStudio.init(params);
62+
63+
I.see('Header 1');
64+
I.see('Option 1.1');
65+
I.see('Header 2');
66+
I.see('Option 2.2');
67+
68+
I.say('Select some choices by pressing hotkeys');
69+
70+
I.pressKey('1');
71+
I.pressKey('q');
72+
I.pressKey('s');
73+
74+
I.say('Check the result');
75+
76+
I.seeElement('.ant-checkbox-checked [name=\'Header 1\']');
77+
I.seeElement('.ant-checkbox-indeterminate [name=\'Header 2\']');
78+
I.seeElement('.ant-checkbox-checked [name=\'Option 1.1\']');
79+
I.seeElement('.ant-checkbox-checked [name=\'Option 1.2\']');
80+
I.seeElement('.ant-checkbox-checked [name=\'Option 2.3\']');
81+
I.seeElement('.ant-checkbox-checked [name=\'Static option\']');
82+
83+
const result = await LabelStudio.serialize();
84+
85+
assert.deepStrictEqual(result.length, 1);
86+
assert.deepStrictEqual(result[0].value.choices, [['Static option'], ['Header 1'], ['Header 1', 'Option 1.1'], ['Header 1', 'Option 1.2'], ['Header 2', 'Option 2.3']]);
87+
88+
});

src/mixins/DynamicChildrenMixin.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ const DynamicChildrenMixin = types.model({
6464
if (!valueFromTask) return;
6565

6666
self.updateWithDynamicChildren(valueFromTask, store);
67-
if (self.annotation) self.needsUpdate?.();
67+
if (self.annotation) {
68+
self.annotation.setupHotKeys();
69+
self.needsUpdate?.();
70+
}
6871
}
6972
},
7073

0 commit comments

Comments
 (0)