Merge pull request #5903 from betagouv/4865-multi-select-accessible

rend accessible le multi-select
This commit is contained in:
krichtof 2021-02-16 17:16:35 +01:00 committed by GitHub
commit 62a1716067
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 459 additions and 213 deletions

View file

@ -5,34 +5,4 @@
.select-instructeurs {
width: 100%;
}
.select2-container--default {
.select2-selection--multiple {
border: solid 1px $border-grey;
.select2-selection__choice, // scss-lint:disable SelectorFormat
.select2-search--inline {
padding: $default-spacer;
}
}
&.select2-container--focus {
.select2-selection--multiple {
border: 1px solid $blue;
box-shadow: 0px 0px 2px 1px $blue;
}
}
.select2-results__option { // scss-lint:disable SelectorFormat
padding: $default-spacer;
}
.custom-select2-option {
.icon {
margin-right: $default-spacer;
}
}
}
}

View file

@ -4,6 +4,5 @@
// = require ./utils
// = require ./fonts
// = require leaflet
// = require select2
// = require_tree .
// = stub ./print.scss

View file

@ -281,18 +281,6 @@
.dropdown-form {
padding: 2 * $default-spacer;
.select2-container {
margin-bottom: 2 * $default-spacer;
}
.select2-selection {
border: 1px solid $border-grey;
&.select2-selection--multiple {
border: 1px solid $border-grey;
}
}
&.large {
width: 340px;
}
@ -310,10 +298,6 @@
}
}
.select2-dropdown {
border: 1px solid $border-grey;
}
.link {
color: $blue;
}

View file

@ -260,8 +260,7 @@
max-width: 180px;
}
select,
.select2-selection {
select {
// hack found here: https://stackoverflow.com/questions/1895476/how-to-style-a-select-dropdown-with-css-only-without-javascript
-webkit-appearance: none;
-moz-appearance: none;
@ -305,39 +304,26 @@
border-color: $blue;
}
.select2 {
min-width: 50%;
[data-reach-combobox-token-list] {
padding: $default-padding;
display: flex;
}
.select2-container {
display: block;
margin-bottom: $default-fields-spacer;
[data-reach-combobox-token] {
border: solid 1px $border-grey;
color: $black;
margin-top: $default-padding;
margin-bottom: $default-padding;
margin-right: 0.5 * $default-padding;
border-radius: 4px;
padding: $default-padding;
cursor: pointer;
list-style: none;
}
&.select2-container--focus {
.select2-selection {
border-color: $border-grey;
}
}
.select2-selection--single {
min-height: 62px;
// scss-lint:disable SelectorFormat
.select2-selection__arrow {
display: none;
}
// scss-lint:enable
}
// scss-lint:disable SelectorFormat
.select2-selection__rendered {
padding: $default-padding;
}
.select2-selection__choice {
background-color: #FFFFFF;
}
// scss-lint:enable
[data-reach-combobox-token]:focus {
background-color: $black;
color: $white;
}
.editable-champ {
@ -481,11 +467,55 @@
}
}
[data-react-class="ComboMultipleDropdownList"] {
margin-bottom: $default-fields-spacer;
[data-reach-combobox-input] {
outline: none;
border: none;
flex-grow: 1;
margin: 0.25rem;
background-image: image-url("icons/chevron-down");
background-size: 14px;
background-repeat: no-repeat;
background-position: right 10px center;
}
[data-reach-combobox-input]:focus {
outline: solid;
outline-color: $light-blue;
}
}
[data-combobox-token-label] {
border: 1px solid #CCCCCC;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
}
[data-reach-combobox-option] {
font-size: 16px;
list-style-type: none;
}
[data-reach-combobox-option][aria-selected="true"] {
background: $light-blue !important;
color: $white;
}
[data-combobox-separator] {
font-size: 16px;
color: $dark-grey;
margin-top: 6px;
}
[data-combobox-remove-token] {
color: $dark-grey;
padding-right: 4px;
}
[data-reach-combobox-input]:focus {
outline-color: $light-blue;
}

View file

@ -1,14 +1,50 @@
@import "constants";
@import "colors";
.personnes-impliquees {
padding-bottom: 50px;
ul {
ul.tab-list {
list-style-type: disc;
margin-left: 16px;
}
// scss-lint:disable SelectorFormat
.form .select2-container .select2-selection__rendered {
padding: 12px;
[data-react-class="ComboMultipleDropdownList"] {
margin-bottom: $default-fields-spacer;
[data-reach-combobox-token-list] {
padding: 0.5 * $default-padding;
display: flex;
}
[data-reach-combobox-token] {
border: solid 1px $border-grey;
color: $black;
margin-top: 0.5 * $default-padding;
margin-bottom: 0.5 * $default-padding;
margin-right: 0.5 * $default-padding;
border-radius: 4px;
padding: 0.5 * $default-padding;
cursor: pointer;
list-style: none;
}
[data-reach-combobox-token]:focus {
background-color: $black;
color: $white;
}
[data-reach-combobox-input] {
outline: none;
border: none;
flex-grow: 1;
margin: 0.25rem;
}
[data-reach-combobox-input]:focus {
outline: solid;
outline-color: $light-blue;
}
}
// scss-lint:enable
}

View file

@ -61,4 +61,42 @@
margin-bottom: 3 * $default-spacer;
text-align: center;
}
[data-react-class="ComboMultipleDropdownList"] {
margin-bottom: $default-fields-spacer;
[data-reach-combobox-token-list] {
padding: 0.25 * $default-padding;
display: inline-block;
width: 100%;
}
[data-reach-combobox-token] {
border: solid 1px $border-grey;
color: $black;
margin: 0.25 * $default-padding;
border-radius: 2px;
padding: 0.25 * $default-padding;
cursor: pointer;
list-style: none;
}
[data-reach-combobox-token]:focus {
background-color: $black;
color: $white;
}
[data-reach-combobox-input] {
outline: none;
border: none;
flex-grow: 1;
margin: 0.25rem;
}
[data-reach-combobox-input]:focus {
outline: solid;
outline-color: $light-blue;
}
}
}

View file

@ -72,7 +72,7 @@ module Instructeurs
end
def send_to_instructeurs
recipients = Instructeur.find(params[:recipients])
recipients = Instructeur.find(JSON.parse(params[:recipients]))
recipients.each do |recipient|
recipient.follow(dossier)

View file

@ -138,7 +138,7 @@ module Instructeurs
end
def update_displayed_fields
procedure_presentation.update_displayed_fields(params[:values])
procedure_presentation.update_displayed_fields(JSON.parse(params[:values]))
redirect_back(fallback_location: instructeur_procedure_url(procedure))
end

View file

@ -80,8 +80,8 @@ module NewAdministrateur
end
def add_instructeur
emails = params['emails'].presence || []
emails = emails.map(&:strip).map(&:downcase)
emails = params['emails'].presence || [].to_json
emails = JSON.parse(emails).map(&:strip).map(&:downcase)
correct_emails, bad_emails = emails
.partition { |email| URI::MailTo::EMAIL_REGEXP.match?(email) }

View file

@ -0,0 +1,250 @@
import React, {
useMemo,
useState,
useRef,
useContext,
createContext,
useEffect,
useLayoutEffect
} from 'react';
import PropTypes from 'prop-types';
import {
Combobox,
ComboboxInput,
ComboboxList,
ComboboxOption,
ComboboxPopover
} from '@reach/combobox';
import '@reach/combobox/styles.css';
import matchSorter from 'match-sorter';
import { fire } from '@utils';
const Context = createContext();
function ComboMultipleDropdownList({
options,
hiddenFieldId,
selected,
label,
acceptNewValues = false
}) {
if (label == undefined) {
label = 'Choisir une option';
}
if (Array.isArray(options[0]) == false) {
options = options.map((o) => [o, o]);
}
const inputRef = useRef();
const [term, setTerm] = useState('');
const [selections, setSelections] = useState(selected);
const [newValues, setNewValues] = useState([]);
const results = useMemo(
() =>
(term
? matchSorter(
options.filter((o) => !o[0].startsWith('--')),
term
)
: options
).filter((o) => o[0] && !selections.includes(o[1])),
[term, selections.join(',')]
);
const hiddenField = useMemo(
() => document.querySelector(`input[data-uuid="${hiddenFieldId}"]`),
[hiddenFieldId]
);
const handleChange = (event) => {
setTerm(event.target.value);
};
const onKeyDown = (event) => {
if (event.key === 'Enter') {
if (term && options.map((o) => o[0]).includes(term)) {
event.preventDefault();
return onSelect(term);
}
if (
acceptNewValues &&
term &&
matchSorter(
options.map((o) => o[0]),
term
).length == 0 // ignore when was pressed for selecting popover option
) {
event.preventDefault();
setNewValues([...newValues, term]);
saveSelection([...selections, term]);
setTerm('');
}
}
};
const saveSelection = (selections) => {
setSelections(selections);
if (hiddenField) {
hiddenField.setAttribute('value', JSON.stringify(selections));
fire(hiddenField, 'autosave:trigger');
}
};
const onSelect = (value) => {
let sel = options.find((o) => o[0] == value)[1];
saveSelection([...selections, sel]);
setTerm('');
};
const onRemove = (value) => {
saveSelection(
selections.filter((s) =>
newValues.includes(value)
? s != value
: s !== options.find((o) => o[0] == value)[1]
)
);
inputRef.current.focus();
};
return (
<Combobox openOnFocus={true} onSelect={onSelect} aria-label={label}>
<ComboboxTokenLabel onRemove={onRemove}>
<ul
aria-live="polite"
aria-atomic={true}
data-reach-combobox-token-list
>
{selections.map((selection) => (
<ComboboxToken
key={selection}
value={
newValues.find((newValue) => newValue == selection) ||
options.find((o) => o[1] == selection)[0]
}
/>
))}
</ul>
<ComboboxInput
ref={inputRef}
value={term}
onChange={handleChange}
onKeyDown={onKeyDown}
autocomplete={false}
/>
</ComboboxTokenLabel>
{results && (
<ComboboxPopover portal={false}>
{results.length === 0 && (
<p>
Aucun résultat{' '}
<button
onClick={() => {
setTerm('');
}}
>
Effacer
</button>
</p>
)}
<ComboboxList>
{results.map((value, index) => {
if (value[0].startsWith('--')) {
return <ComboboxSeparator key={index} value={value[0]} />;
}
return <ComboboxOption key={index} value={value[0]} />;
})}
</ComboboxList>
</ComboboxPopover>
)}
</Combobox>
);
}
function ComboboxTokenLabel({ onRemove, ...props }) {
const selectionsRef = useRef([]);
useLayoutEffect(() => {
selectionsRef.current = [];
return () => (selectionsRef.current = []);
});
const context = {
onRemove,
selectionsRef
};
return (
<Context.Provider value={context}>
<div data-combobox-token-label {...props} />
</Context.Provider>
);
}
ComboboxTokenLabel.propTypes = {
onRemove: PropTypes.func
};
function ComboboxSeparator({ value }) {
return (
<li aria-disabled="true" role="option" data-combobox-separator>
{value.slice(2, -2)}
</li>
);
}
ComboboxSeparator.propTypes = {
value: PropTypes.string
};
function ComboboxToken({ value, ...props }) {
const { selectionsRef, onRemove } = useContext(Context);
useEffect(() => {
selectionsRef.current.push(value);
});
return (
<li
data-reach-combobox-token
tabIndex="0"
onKeyDown={(event) => {
if (event.key === 'Backspace') {
onRemove(value);
}
}}
{...props}
>
<span
role="presentation"
data-combobox-remove-token
onClick={() => {
onRemove(value);
}}
>
x
</span>
{value}
</li>
);
}
ComboboxToken.propTypes = {
value: PropTypes.string,
label: PropTypes.string
};
ComboMultipleDropdownList.propTypes = {
options: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.arrayOf(
PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
)
)
]),
hiddenFieldId: PropTypes.string,
selected: PropTypes.arrayOf(PropTypes.string),
arraySelected: PropTypes.arrayOf(PropTypes.array),
label: PropTypes.string,
acceptNewValues: PropTypes.bool
};
export default ComboMultipleDropdownList;

View file

@ -0,0 +1,5 @@
import Loadable from '../components/Loadable';
export default Loadable(() =>
import('../components/ComboMultipleDropdownList')
);

View file

@ -1,80 +0,0 @@
import $ from 'jquery';
import 'select2';
const language = {
errorLoading: function () {
return 'Les résultats ne peuvent pas être chargés.';
},
inputTooLong: function (args) {
var overChars = args.input.length - args.maximum;
return 'Supprimez ' + overChars + ' caractère' + (overChars > 1 ? 's' : '');
},
inputTooShort: function (args) {
var remainingChars = args.minimum - args.input.length;
return (
'Saisissez au moins ' +
remainingChars +
' caractère' +
(remainingChars > 1 ? 's' : '')
);
},
loadingMore: function () {
return 'Chargement de résultats supplémentaires…';
},
maximumSelected: function (args) {
return (
'Vous pouvez seulement sélectionner ' +
args.maximum +
' élément' +
(args.maximum > 1 ? 's' : '')
);
},
noResults: function () {
return 'Aucun résultat trouvé';
},
searching: function () {
return 'Recherche en cours…';
},
removeAllItems: function () {
return 'Supprimer tous les éléments';
}
};
const baseOptions = {
language,
width: '100%'
};
const templateOption = ({ text }) =>
$(
`<span class="custom-select2-option"><span class="icon person"></span>${text}</span>`
);
addEventListener('ds:page:update', () => {
$('select.select2').select2(baseOptions);
$('.columns-form select.select2-limited').select2({
width: '300px',
placeholder: 'Sélectionnez des colonnes',
maximumSelectionLength: '5'
});
$('.recipients-form select.select2-limited').select2({
language,
width: '300px',
placeholder: 'Sélectionnez des instructeurs',
maximumSelectionLength: '30'
});
$('select.select2-limited.select-instructeurs').select2({
language,
dropdownParent: $('.instructeur-wrapper'),
placeholder: 'Saisir ladresse email de linstructeur',
tags: true,
tokenSeparators: [',', ' '],
templateResult: templateOption,
templateSelection: templateOption
});
});

View file

@ -17,7 +17,6 @@ import '../new_design/dropdown';
import '../new_design/form-validation';
import '../new_design/procedure-context';
import '../new_design/procedure-form';
import '../new_design/select2';
import '../new_design/spinner';
import '../new_design/support';
import '../new_design/dossiers/auto-save';

View file

@ -8,9 +8,8 @@
Le destinataire suivra automatiquement le dossier
= form_for dossier, url: send_to_instructeurs_instructeur_dossier_path(dossier.procedure, dossier), method: :post, html: { class: 'form recipients-form' } do |f|
.flex.justify-start.align-start
= select_tag(:recipients,
options_from_collection_for_select(potential_recipients, :id, :email),
multiple: true,
class: 'select2-limited',
placeholder: '')
- hidden_field_id = SecureRandom.uuid
= hidden_field_tag :recipients, nil, data: { uuid: hidden_field_id }
= react_component("ComboMultipleDropdownList", options: potential_recipients.map{|r| [r.email, r.id]}, selected: [], disabled: [], hiddenFieldId: hidden_field_id, label: "email instructeur")
= f.submit "Envoyer", class: "button large send gap-left"

View file

@ -122,10 +122,10 @@
Personnaliser
#custom-menu.dropdown-content.fade-in-down
= form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form columns-form' do
= select_tag :values,
options_for_select(@displayed_fields_options, selected: @displayed_fields_selected),
multiple: true,
class: 'select2-limited'
- hidden_field_id = SecureRandom.uuid
= hidden_field_tag :values, nil, data: { uuid: hidden_field_id }
= react_component("ComboMultipleDropdownList", options: @displayed_fields_options, selected: @displayed_fields_selected, disabled: [], hiddenFieldId: hidden_field_id, label: 'colonne')
= submit_tag "Enregistrer", class: 'button'
%tbody

View file

@ -25,12 +25,15 @@
.instructeur-wrapper
- if !@procedure.routee?
%p.notice Entrez les adresses email des instructeurs que vous souhaitez affecter à cette démarche
= select_tag :emails,
options_for_select(@available_instructeur_emails),
multiple: true,
class: 'select-instructeurs select2-limited'
- hidden_field_id = SecureRandom.uuid
= hidden_field_tag :emails, nil, data: { uuid: hidden_field_id }
= react_component("ComboMultipleDropdownList",
options: @available_instructeur_emails, selected: [], disabled: [],
hiddenFieldId: hidden_field_id,
label: 'email instructeur',
acceptNewValues: true)
= f.submit 'Affecter', class: 'button primary send'
= f.submit 'Affecter', class: 'button primary send'
%table.table.mt-2
%thead

View file

@ -7,10 +7,7 @@
= b.text
- else
= form.select :value,
champ.options,
{ selected: champ.selected_options,
disabled: champ.disabled_options },
multiple: true,
class: 'select2'
- hidden_field_id = SecureRandom.uuid
= form.hidden_field :value, { data: { uuid: hidden_field_id } }
= react_component("ComboMultipleDropdownList", options: champ.options, selected: champ.selected_options, disabled: champ.disabled_options, hiddenFieldId: hidden_field_id, label: champ.libelle)

View file

@ -38,7 +38,7 @@ describe Instructeurs::DossiersController, type: :controller do
post(
:send_to_instructeurs,
params: {
recipients: [recipient],
recipients: [recipient.id].to_json,
procedure_id: procedure.id,
dossier_id: dossier.id
}

View file

@ -207,11 +207,11 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
describe '#add_instructeur_procedure_non_routee' do
let(:procedure) { create :procedure, administrateur: admin }
let(:emails) { ['instructeur_3@ministere_a.gouv.fr', 'instructeur_4@ministere_b.gouv.fr'] }
let(:emails) { ['instructeur_3@ministere_a.gouv.fr', 'instructeur_4@ministere_b.gouv.fr'].to_json }
subject { post :add_instructeur, params: { emails: emails, procedure_id: procedure.id, id: gi_1_1.id } }
context 'when all emails are valid' do
let(:emails) { ['test@b.gouv.fr', 'test2@b.gouv.fr'] }
let(:emails) { ['test@b.gouv.fr', 'test2@b.gouv.fr'].to_json }
it { expect(response.status).to eq(200) }
it { expect(subject.request.flash[:alert]).to be_nil }
it { expect(subject.request.flash[:notice]).to be_present }
@ -219,7 +219,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end
context 'when there is at least one bad email' do
let(:emails) { ['badmail', 'instructeur2@gmail.com'] }
let(:emails) { ['badmail', 'instructeur2@gmail.com'].to_json }
it { expect(response.status).to eq(200) }
it { expect(subject.request.flash[:alert]).to be_present }
it { expect(subject.request.flash[:notice]).to be_present }
@ -227,7 +227,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end
context 'when the admin wants to assign an instructor who is already assigned on this procedure' do
let(:emails) { ['instructeur_1@ministere_a.gouv.fr'] }
let(:emails) { ['instructeur_1@ministere_a.gouv.fr'].to_json }
it { expect(subject.request.flash[:alert]).to be_present }
it { expect(subject).to redirect_to admin_procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur) }
end
@ -247,7 +247,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
params: {
procedure_id: procedure.id,
id: gi_1_2.id,
emails: new_instructeur_emails
emails: new_instructeur_emails.to_json
}
end
@ -281,7 +281,7 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
end
context 'of an empty string' do
let(:new_instructeur_emails) { '' }
let(:new_instructeur_emails) { [''] }
it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) }
end

View file

@ -128,10 +128,8 @@ feature 'Instructing a dossier:' do
click_on 'Personnes impliquées'
first('.select2-container', minimum: 1).click
find('li.select2-results__option[role="option"]', text: instructeur_2.email).click
first('.select2-container', minimum: 1).click
find('li.select2-results__option[role="option"]', text: instructeur_3.email).click
select_multi('email instructeur', instructeur_2.email)
select_multi('email instructeur', instructeur_3.email)
click_on 'Envoyer'

View file

@ -125,15 +125,13 @@ feature "procedure filters" do
def add_column(column_name)
click_on 'Personnaliser'
find("span.select2-container").click
find(:xpath, "//li[text()='#{column_name}']").click
select_multi('colonne', column_name)
click_button "Enregistrer"
end
def remove_column(column_name)
click_on 'Personnaliser'
find(:xpath, "//li[contains(@title, '#{column_name}')]/span[contains(text(), '×')]").click
find(:xpath, "//form[contains(@class, 'columns-form')]//span[contains(@class, 'select2-container')]").click
find(:xpath, "//li[contains(text(), '#{column_name}')]/span[contains(text(), 'x')]").click
click_button "Enregistrer"
end
end

View file

@ -30,14 +30,14 @@ feature 'The routing', js: true do
expect(page).to have_field('Nom du groupe', with: 'littéraire')
# add victor to littéraire groupe
find('input.select2-search__field').send_keys('victor@inst.com', :enter)
find("input[aria-label='email instructeur'").send_keys('victor@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche")
victor = User.find_by(email: 'victor@inst.com').instructeur
# add superwoman to littéraire groupe
find('input.select2-search__field').send_keys('superwoman@inst.com', :enter)
find("input[aria-label='email instructeur'").send_keys('superwoman@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche")
@ -50,14 +50,14 @@ feature 'The routing', js: true do
expect(page).to have_text('Le groupe dinstructeurs « scientifique » a été créé.')
# add marie to scientifique groupe
find('input.select2-search__field').send_keys('marie@inst.com', :enter)
find("input[aria-label='email instructeur'").send_keys('marie@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur marie@inst.com a été affecté")
marie = User.find_by(email: 'marie@inst.com').instructeur
# add superwoman to scientifique groupe
find('input.select2-search__field').send_keys('superwoman@inst.com', :enter)
find("input[aria-label='email instructeur'").send_keys('superwoman@inst.com', :enter)
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur superwoman@inst.com a été affecté")

View file

@ -25,8 +25,9 @@ feature 'The user' do
check('val1')
check('val3')
select('bravo', from: form_id_for('simple_choice_drop_down_list_long'))
select('alpha', from: form_id_for('multiple_choice_drop_down_list_long'))
select('charly', from: form_id_for('multiple_choice_drop_down_list_long'))
select_multi('multiple_choice_drop_down_list_long', 'alpha')
select_multi('multiple_choice_drop_down_list_long', 'charly')
select_champ_geo('pays', 'aust', 'AUSTRALIE')
select_champ_geo('regions', 'Ma', 'Martinique')
@ -83,7 +84,7 @@ feature 'The user' do
expect(page).to have_checked_field('val1')
expect(page).to have_checked_field('val3')
expect(page).to have_selected_value('simple_choice_drop_down_list_long', selected: 'bravo')
expect(page).to have_selected_value('multiple_choice_drop_down_list_long', selected: ['alpha', 'charly'])
check_selected_values('multiple_choice_drop_down_list_long', ['alpha', 'charly'])
expect(page).to have_hidden_field('pays', with: 'AUSTRALIE')
expect(page).to have_hidden_field('regions', with: 'Martinique')
expect(page).to have_hidden_field('departements', with: '02 - Aisne')

View file

@ -103,6 +103,25 @@ module FeatureHelpers
end
end
def select_multi(champ, with)
input = find("input[aria-label='#{champ}'")
input.click
# hack because for unknown reason, the click on input doesn't show combobox-popover with selenium driver
script = "document.evaluate(\"//input[@aria-label='#{champ}']//ancestor::div[@data-reach-combobox]/div[@data-reach-combobox-popover]\", document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null).iterateNext().removeAttribute(\"hidden\")"
execute_script(script)
element = find(:xpath, "//input[@aria-label='#{champ}']/ancestor::div[@data-reach-combobox]//div[@data-reach-combobox-popover]//li/span[normalize-space(text())='#{with}']")
element.click
end
def check_selected_values(champ, values)
combobox = find(:xpath, "//input[@aria-label='#{champ}']/ancestor::div[@data-react-class='ComboMultipleDropdownList']")
hiddenFieldId = JSON.parse(combobox["data-react-props"])["hiddenFieldId"]
hiddenField = find("input[data-uuid='#{hiddenFieldId}']")
expect(values.sort).to eq(JSON.parse(hiddenField.value).sort)
end
# Keep the brower window open after a test success of failure, to
# allow inspecting the page or the console.
#

View file

@ -13,7 +13,7 @@ describe 'instructeurs/dossiers/envoyer_dossier_block.html.haml', type: :view do
let(:instructeur) { create(:instructeur, email: 'yop@totomail.fr') }
let(:potential_recipients) { [instructeur] }
it { is_expected.to have_css("select > option[value='#{instructeur.id}']") }
it { is_expected.to match(/data-react-props.*#{instructeur.email}/) }
it { is_expected.to have_css(".button.send") }
end

View file

@ -82,7 +82,7 @@ describe 'shared/dossiers/edit.html.haml', type: :view do
let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list, :long, procedure: dossier.procedure) }
it 'renders the list as a multiple-selection dropdown' do
expect(subject).to have_selector('select.select2')
expect(subject).to have_selector('[data-react-class="ComboMultipleDropdownList"]')
end
end
end