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 { .select-instructeurs {
width: 100%; 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 ./utils
// = require ./fonts // = require ./fonts
// = require leaflet // = require leaflet
// = require select2
// = require_tree . // = require_tree .
// = stub ./print.scss // = stub ./print.scss

View file

@ -281,18 +281,6 @@
.dropdown-form { .dropdown-form {
padding: 2 * $default-spacer; 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 { &.large {
width: 340px; width: 340px;
} }
@ -310,10 +298,6 @@
} }
} }
.select2-dropdown {
border: 1px solid $border-grey;
}
.link { .link {
color: $blue; color: $blue;
} }

View file

@ -260,8 +260,7 @@
max-width: 180px; max-width: 180px;
} }
select, select {
.select2-selection {
// hack found here: https://stackoverflow.com/questions/1895476/how-to-style-a-select-dropdown-with-css-only-without-javascript // hack found here: https://stackoverflow.com/questions/1895476/how-to-style-a-select-dropdown-with-css-only-without-javascript
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
@ -305,39 +304,26 @@
border-color: $blue; border-color: $blue;
} }
.select2 { [data-reach-combobox-token-list] {
min-width: 50%; padding: $default-padding;
display: flex;
} }
.select2-container { [data-reach-combobox-token] {
display: block; border: solid 1px $border-grey;
margin-bottom: $default-fields-spacer; 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 { [data-reach-combobox-token]:focus {
.select2-selection { background-color: $black;
border-color: $border-grey; color: $white;
}
}
.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
} }
.editable-champ { .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] { [data-reach-combobox-option] {
font-size: 16px; font-size: 16px;
list-style-type: none;
} }
[data-reach-combobox-option][aria-selected="true"] { [data-reach-combobox-option][aria-selected="true"] {
background: $light-blue !important; background: $light-blue !important;
color: $white; 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 { .personnes-impliquees {
padding-bottom: 50px; padding-bottom: 50px;
ul { ul.tab-list {
list-style-type: disc; list-style-type: disc;
margin-left: 16px; margin-left: 16px;
} }
// scss-lint:disable SelectorFormat [data-react-class="ComboMultipleDropdownList"] {
.form .select2-container .select2-selection__rendered { margin-bottom: $default-fields-spacer;
padding: 12px;
[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; margin-bottom: 3 * $default-spacer;
text-align: center; 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 end
def send_to_instructeurs def send_to_instructeurs
recipients = Instructeur.find(params[:recipients]) recipients = Instructeur.find(JSON.parse(params[:recipients]))
recipients.each do |recipient| recipients.each do |recipient|
recipient.follow(dossier) recipient.follow(dossier)

View file

@ -138,7 +138,7 @@ module Instructeurs
end end
def update_displayed_fields 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)) redirect_back(fallback_location: instructeur_procedure_url(procedure))
end end

View file

@ -80,8 +80,8 @@ module NewAdministrateur
end end
def add_instructeur def add_instructeur
emails = params['emails'].presence || [] emails = params['emails'].presence || [].to_json
emails = emails.map(&:strip).map(&:downcase) emails = JSON.parse(emails).map(&:strip).map(&:downcase)
correct_emails, bad_emails = emails correct_emails, bad_emails = emails
.partition { |email| URI::MailTo::EMAIL_REGEXP.match?(email) } .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/form-validation';
import '../new_design/procedure-context'; import '../new_design/procedure-context';
import '../new_design/procedure-form'; import '../new_design/procedure-form';
import '../new_design/select2';
import '../new_design/spinner'; import '../new_design/spinner';
import '../new_design/support'; import '../new_design/support';
import '../new_design/dossiers/auto-save'; import '../new_design/dossiers/auto-save';

View file

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

View file

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

View file

@ -25,12 +25,15 @@
.instructeur-wrapper .instructeur-wrapper
- if !@procedure.routee? - if !@procedure.routee?
%p.notice Entrez les adresses email des instructeurs que vous souhaitez affecter à cette démarche %p.notice Entrez les adresses email des instructeurs que vous souhaitez affecter à cette démarche
= select_tag :emails, - hidden_field_id = SecureRandom.uuid
options_for_select(@available_instructeur_emails), = hidden_field_tag :emails, nil, data: { uuid: hidden_field_id }
multiple: true, = react_component("ComboMultipleDropdownList",
class: 'select-instructeurs select2-limited' 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 %table.table.mt-2
%thead %thead

View file

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

View file

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

View file

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

View file

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

View file

@ -125,15 +125,13 @@ feature "procedure filters" do
def add_column(column_name) def add_column(column_name)
click_on 'Personnaliser' click_on 'Personnaliser'
find("span.select2-container").click select_multi('colonne', column_name)
find(:xpath, "//li[text()='#{column_name}']").click
click_button "Enregistrer" click_button "Enregistrer"
end end
def remove_column(column_name) def remove_column(column_name)
click_on 'Personnaliser' click_on 'Personnaliser'
find(:xpath, "//li[contains(@title, '#{column_name}')]/span[contains(text(), '×')]").click find(:xpath, "//li[contains(text(), '#{column_name}')]/span[contains(text(), 'x')]").click
find(:xpath, "//form[contains(@class, 'columns-form')]//span[contains(@class, 'select2-container')]").click
click_button "Enregistrer" click_button "Enregistrer"
end end
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') expect(page).to have_field('Nom du groupe', with: 'littéraire')
# add victor to littéraire groupe # 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' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche") expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche")
victor = User.find_by(email: 'victor@inst.com').instructeur victor = User.find_by(email: 'victor@inst.com').instructeur
# add superwoman to littéraire groupe # 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' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche") 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éé.') expect(page).to have_text('Le groupe dinstructeurs « scientifique » a été créé.')
# add marie to scientifique groupe # 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' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur marie@inst.com a été affecté") expect(page).to have_text("Linstructeur marie@inst.com a été affecté")
marie = User.find_by(email: 'marie@inst.com').instructeur marie = User.find_by(email: 'marie@inst.com').instructeur
# add superwoman to scientifique groupe # 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' } perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur superwoman@inst.com a été affecté") 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('val1')
check('val3') check('val3')
select('bravo', from: form_id_for('simple_choice_drop_down_list_long')) 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_multi('multiple_choice_drop_down_list_long', 'alpha')
select('charly', from: form_id_for('multiple_choice_drop_down_list_long')) select_multi('multiple_choice_drop_down_list_long', 'charly')
select_champ_geo('pays', 'aust', 'AUSTRALIE') select_champ_geo('pays', 'aust', 'AUSTRALIE')
select_champ_geo('regions', 'Ma', 'Martinique') 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('val1')
expect(page).to have_checked_field('val3') 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('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('pays', with: 'AUSTRALIE')
expect(page).to have_hidden_field('regions', with: 'Martinique') expect(page).to have_hidden_field('regions', with: 'Martinique')
expect(page).to have_hidden_field('departements', with: '02 - Aisne') expect(page).to have_hidden_field('departements', with: '02 - Aisne')

View file

@ -103,6 +103,25 @@ module FeatureHelpers
end end
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 # Keep the brower window open after a test success of failure, to
# allow inspecting the page or the console. # 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(:instructeur) { create(:instructeur, email: 'yop@totomail.fr') }
let(:potential_recipients) { [instructeur] } 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") } it { is_expected.to have_css(".button.send") }
end 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) } 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 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 end
end end