698 lines
32 KiB
Python
698 lines
32 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
from freezegun import freeze_time
|
||
|
|
||
|
from odoo import _, Command, fields
|
||
|
from odoo.addons.mail.tests.common import MailCase
|
||
|
from odoo.addons.survey.tests import common
|
||
|
from odoo.tests.common import users
|
||
|
|
||
|
|
||
|
class TestSurveyInternals(common.TestSurveyCommon, MailCase):
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_allowed_triggering_question_ids(self):
|
||
|
# Create 2 surveys, each with 3 questions, each with 2 suggested answers
|
||
|
survey_1, survey_2 = self.env['survey.survey'].create([
|
||
|
{'title': 'Test Survey 1'},
|
||
|
{'title': 'Test Survey 2'}
|
||
|
])
|
||
|
self.env['survey.question'].create([
|
||
|
{
|
||
|
'survey_id': survey_id,
|
||
|
'title': f'Question {question_idx}',
|
||
|
'question_type': 'simple_choice',
|
||
|
'suggested_answer_ids': [
|
||
|
Command.create({
|
||
|
'value': f'Answer {answer_idx}',
|
||
|
}) for answer_idx in range(2)],
|
||
|
}
|
||
|
for question_idx in range(3)
|
||
|
for survey_id in (survey_1 | survey_2).ids
|
||
|
])
|
||
|
survey_1_q_1, survey_1_q_2, _ = survey_1.question_ids
|
||
|
survey_2_q_1, survey_2_q_2, _ = survey_2.question_ids
|
||
|
|
||
|
with self.subTest('Editing existing questions'):
|
||
|
# Only previous questions from the same survey
|
||
|
self.assertFalse(bool(survey_1_q_2.allowed_triggering_question_ids & survey_2_q_2.allowed_triggering_question_ids))
|
||
|
self.assertEqual(survey_1_q_2.allowed_triggering_question_ids, survey_1_q_1)
|
||
|
self.assertEqual(survey_2_q_2.allowed_triggering_question_ids, survey_2_q_1)
|
||
|
|
||
|
survey_1_new_question = self.env['survey.question'].new({'survey_id': survey_1})
|
||
|
survey_2_new_question = self.env['survey.question'].new({'survey_id': survey_2})
|
||
|
|
||
|
with self.subTest('New questions'):
|
||
|
# New questions should be allowed to use any question with choices from the same survey
|
||
|
self.assertFalse(
|
||
|
bool(survey_1_new_question.allowed_triggering_question_ids & survey_2_new_question.allowed_triggering_question_ids)
|
||
|
)
|
||
|
self.assertEqual(survey_1_new_question.allowed_triggering_question_ids.ids, survey_1.question_ids.ids)
|
||
|
self.assertEqual(survey_2_new_question.allowed_triggering_question_ids.ids, survey_2.question_ids.ids)
|
||
|
|
||
|
def test_answer_attempts_count(self):
|
||
|
""" As 'attempts_number' and 'attempts_count' are computed using raw SQL queries, let us
|
||
|
test the results. """
|
||
|
|
||
|
test_survey = self.env['survey.survey'].create({
|
||
|
'title': 'Test Survey',
|
||
|
'is_attempts_limited': True,
|
||
|
'attempts_limit': 4,
|
||
|
})
|
||
|
|
||
|
all_attempts = self.env['survey.user_input']
|
||
|
for _i in range(4):
|
||
|
all_attempts |= self._add_answer(test_survey, self.survey_user.partner_id, state='done')
|
||
|
|
||
|
# read both fields at once to allow computing their values in batch
|
||
|
attempts_results = all_attempts.read(['attempts_number', 'attempts_count'])
|
||
|
first_attempt = attempts_results[0]
|
||
|
second_attempt = attempts_results[1]
|
||
|
third_attempt = attempts_results[2]
|
||
|
fourth_attempt = attempts_results[3]
|
||
|
|
||
|
self.assertEqual(first_attempt['attempts_number'], 1)
|
||
|
self.assertEqual(first_attempt['attempts_count'], 4)
|
||
|
|
||
|
self.assertEqual(second_attempt['attempts_number'], 2)
|
||
|
self.assertEqual(second_attempt['attempts_count'], 4)
|
||
|
|
||
|
self.assertEqual(third_attempt['attempts_number'], 3)
|
||
|
self.assertEqual(third_attempt['attempts_count'], 4)
|
||
|
|
||
|
self.assertEqual(fourth_attempt['attempts_number'], 4)
|
||
|
self.assertEqual(fourth_attempt['attempts_count'], 4)
|
||
|
|
||
|
@freeze_time("2020-02-15 18:00")
|
||
|
def test_answer_display_name(self):
|
||
|
""" The "display_name" field in a survey.user_input.line is a computed field that will
|
||
|
display the answer label for any type of question.
|
||
|
Let us test the various question types. """
|
||
|
|
||
|
questions = self._create_one_question_per_type()
|
||
|
user_input = self._add_answer(self.survey, self.survey_user.partner_id)
|
||
|
|
||
|
for question in questions:
|
||
|
if question.question_type == 'char_box':
|
||
|
question_answer = self._add_answer_line(question, user_input, 'Char box answer')
|
||
|
self.assertEqual(question_answer.display_name, 'Char box answer')
|
||
|
elif question.question_type == 'text_box':
|
||
|
question_answer = self._add_answer_line(question, user_input, 'Text box answer')
|
||
|
self.assertEqual(question_answer.display_name, 'Text box answer')
|
||
|
elif question.question_type == 'numerical_box':
|
||
|
question_answer = self._add_answer_line(question, user_input, 7)
|
||
|
self.assertEqual(question_answer.display_name, '7.0')
|
||
|
elif question.question_type == 'date':
|
||
|
question_answer = self._add_answer_line(question, user_input, fields.Datetime.now())
|
||
|
self.assertEqual(question_answer.display_name, '2020-02-15')
|
||
|
elif question.question_type == 'datetime':
|
||
|
question_answer = self._add_answer_line(question, user_input, fields.Datetime.now())
|
||
|
self.assertEqual(question_answer.display_name, '2020-02-15 18:00:00')
|
||
|
elif question.question_type == 'simple_choice':
|
||
|
question_answer = self._add_answer_line(question, user_input, question.suggested_answer_ids[0].id)
|
||
|
self.assertEqual(question_answer.display_name, 'SChoice0')
|
||
|
elif question.question_type == 'multiple_choice':
|
||
|
question_answer_1 = self._add_answer_line(question, user_input, question.suggested_answer_ids[0].id)
|
||
|
self.assertEqual(question_answer_1.display_name, 'MChoice0')
|
||
|
question_answer_2 = self._add_answer_line(question, user_input, question.suggested_answer_ids[1].id)
|
||
|
self.assertEqual(question_answer_2.display_name, 'MChoice1')
|
||
|
elif question.question_type == 'matrix':
|
||
|
question_answer_1 = self._add_answer_line(question, user_input,
|
||
|
question.suggested_answer_ids[0].id, **{'answer_value_row': question.matrix_row_ids[0].id})
|
||
|
self.assertEqual(question_answer_1.display_name, 'Column0: Row0')
|
||
|
question_answer_2 = self._add_answer_line(question, user_input,
|
||
|
question.suggested_answer_ids[0].id, **{'answer_value_row': question.matrix_row_ids[1].id})
|
||
|
self.assertEqual(question_answer_2.display_name, 'Column0: Row1')
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_answer_validation_mandatory(self):
|
||
|
""" For each type of question check that mandatory questions correctly check for complete answers """
|
||
|
for question in self._create_one_question_per_type():
|
||
|
self.assertDictEqual(
|
||
|
question.validate_question(''),
|
||
|
{question.id: 'TestError'}
|
||
|
)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_answer_validation_date(self):
|
||
|
question = self._add_question(
|
||
|
self.page_0, 'Q0', 'date', validation_required=True,
|
||
|
validation_min_date='2015-03-20', validation_max_date='2015-03-25', validation_error_msg='ValidationError')
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('Is Alfred an answer?'),
|
||
|
{question.id: _('This is not a date')}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('2015-03-19'),
|
||
|
{question.id: 'ValidationError'}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('2015-03-26'),
|
||
|
{question.id: 'ValidationError'}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('2015-03-25'),
|
||
|
{}
|
||
|
)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_answer_validation_numerical(self):
|
||
|
question = self._add_question(
|
||
|
self.page_0, 'Q0', 'numerical_box', validation_required=True,
|
||
|
validation_min_float_value=2.2, validation_max_float_value=3.3, validation_error_msg='ValidationError')
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('Is Alfred an answer?'),
|
||
|
{question.id: _('This is not a number')}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('2.0'),
|
||
|
{question.id: 'ValidationError'}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('4.0'),
|
||
|
{question.id: 'ValidationError'}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('2.9'),
|
||
|
{}
|
||
|
)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_answer_validation_char_box_email(self):
|
||
|
question = self._add_question(self.page_0, 'Q0', 'char_box', validation_email=True)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('not an email'),
|
||
|
{question.id: _('This answer must be an email address')}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('email@example.com'),
|
||
|
{}
|
||
|
)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_answer_validation_char_box_length(self):
|
||
|
question = self._add_question(
|
||
|
self.page_0, 'Q0', 'char_box', validation_required=True,
|
||
|
validation_length_min=2, validation_length_max=8, validation_error_msg='ValidationError')
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('l'),
|
||
|
{question.id: 'ValidationError'}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('waytoomuchlonganswer'),
|
||
|
{question.id: 'ValidationError'}
|
||
|
)
|
||
|
|
||
|
self.assertEqual(
|
||
|
question.validate_question('valid'),
|
||
|
{}
|
||
|
)
|
||
|
|
||
|
def test_partial_scores_simple_choice(self):
|
||
|
"""" Check that if partial scores are given for partially correct answers, in the case of a multiple
|
||
|
choice question with single choice, choosing the answer with max score gives 100% of points. """
|
||
|
|
||
|
partial_scores_survey = self.env['survey.survey'].create({
|
||
|
'title': 'How much do you know about words?',
|
||
|
'scoring_type': 'scoring_with_answers',
|
||
|
'scoring_success_min': 90.0,
|
||
|
})
|
||
|
[a_01, a_02, a_03] = self.env['survey.question.answer'].create([{
|
||
|
'value': 'A thing full of letters.',
|
||
|
'answer_score': 1.0
|
||
|
}, {
|
||
|
'value': 'A unit of language, [...], carrying a meaning.',
|
||
|
'answer_score': 4.0,
|
||
|
'is_correct': True
|
||
|
}, {
|
||
|
'value': '42',
|
||
|
'answer_score': -4.0
|
||
|
}])
|
||
|
q_01 = self.env['survey.question'].create({
|
||
|
'survey_id': partial_scores_survey.id,
|
||
|
'title': 'What is a word?',
|
||
|
'sequence': 1,
|
||
|
'question_type': 'simple_choice',
|
||
|
'suggested_answer_ids': [(6, 0, (a_01 | a_02 | a_03).ids)]
|
||
|
})
|
||
|
|
||
|
user_input = self.env['survey.user_input'].create({'survey_id': partial_scores_survey.id})
|
||
|
self.env['survey.user_input.line'].create({
|
||
|
'user_input_id': user_input.id,
|
||
|
'question_id': q_01.id,
|
||
|
'answer_type': 'suggestion',
|
||
|
'suggested_answer_id': a_02.id
|
||
|
})
|
||
|
|
||
|
# Check that scoring is correct and survey is passed
|
||
|
self.assertEqual(user_input.scoring_percentage, 100)
|
||
|
self.assertTrue(user_input.scoring_success)
|
||
|
|
||
|
def test_simple_choice_question_answer_result(self):
|
||
|
test_survey = self.env['survey.survey'].create({
|
||
|
'title': 'Test This Survey',
|
||
|
'scoring_type': 'scoring_with_answers',
|
||
|
'scoring_success_min': 80.0,
|
||
|
})
|
||
|
[a_01, a_02, a_03, a_04] = self.env['survey.question.answer'].create([{
|
||
|
'value': 'In Europe',
|
||
|
'answer_score': 0.0,
|
||
|
'is_correct': False
|
||
|
}, {
|
||
|
'value': 'In Asia',
|
||
|
'answer_score': 5.0,
|
||
|
'is_correct': True
|
||
|
}, {
|
||
|
'value': 'In South Asia',
|
||
|
'answer_score': 10.0,
|
||
|
'is_correct': True
|
||
|
}, {
|
||
|
'value': 'On Globe',
|
||
|
'answer_score': 5.0,
|
||
|
'is_correct': False
|
||
|
}])
|
||
|
q_01 = self.env['survey.question'].create({
|
||
|
'survey_id': test_survey.id,
|
||
|
'title': 'Where is india?',
|
||
|
'sequence': 1,
|
||
|
'question_type': 'simple_choice',
|
||
|
'suggested_answer_ids': [(6, 0, (a_01 | a_02 | a_03 | a_04).ids)]
|
||
|
})
|
||
|
|
||
|
user_input = self.env['survey.user_input'].create({'survey_id': test_survey.id})
|
||
|
user_input_line = self.env['survey.user_input.line'].create({
|
||
|
'user_input_id': user_input.id,
|
||
|
'question_id': q_01.id,
|
||
|
'answer_type': 'suggestion',
|
||
|
'suggested_answer_id': a_01.id
|
||
|
})
|
||
|
|
||
|
# this answer is incorrect with no score: should be considered as incorrect
|
||
|
statistics = user_input._prepare_statistics()[user_input]
|
||
|
self.assertAnswerStatus('Incorrect', statistics)
|
||
|
|
||
|
# this answer is correct with a positive score (even if not the maximum): should be considered as correct
|
||
|
user_input_line.suggested_answer_id = a_02.id
|
||
|
statistics = user_input._prepare_statistics()[user_input]
|
||
|
self.assertAnswerStatus('Correct', statistics)
|
||
|
|
||
|
# this answer is correct with the best score: should be considered as correct
|
||
|
user_input_line.suggested_answer_id = a_03.id
|
||
|
statistics = user_input._prepare_statistics()[user_input]
|
||
|
self.assertAnswerStatus('Correct', statistics)
|
||
|
|
||
|
# this answer is incorrect but has a score: should be considered as "partially"
|
||
|
user_input_line.suggested_answer_id = a_04.id
|
||
|
statistics = user_input._prepare_statistics()[user_input]
|
||
|
self.assertAnswerStatus('Partially', statistics)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_skipped_values(self):
|
||
|
""" Create one question per type of questions.
|
||
|
Make sure they are correctly registered as 'skipped' after saving an empty answer for each
|
||
|
of them. """
|
||
|
|
||
|
questions = self._create_one_question_per_type()
|
||
|
survey_user = self.survey._create_answer(user=self.survey_user)
|
||
|
|
||
|
for question in questions:
|
||
|
answer = '' if question.question_type in ['char_box', 'text_box'] else None
|
||
|
survey_user._save_lines(question, answer)
|
||
|
|
||
|
for question in questions:
|
||
|
self._assert_skipped_question(question, survey_user)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_copy_conditional_question_settings(self):
|
||
|
""" Create a survey with conditional layout, clone it and verify that the cloned survey has the same conditional
|
||
|
layout as the original survey.
|
||
|
The test also check that the cloned survey doesn't reference the original survey.
|
||
|
"""
|
||
|
def get_question_by_title(survey, title):
|
||
|
return survey.question_ids.filtered(lambda q: q.title == title)[0]
|
||
|
|
||
|
# Create the survey questions (! texts of the questions must be unique as they are used to query them)
|
||
|
q_is_vegetarian_text = 'Are you vegetarian?'
|
||
|
q_is_vegetarian = self._add_question(
|
||
|
self.page_0, q_is_vegetarian_text, 'multiple_choice', survey_id=self.survey.id,
|
||
|
sequence=100, labels=[{'value': 'Yes'}, {'value': 'No'}, {'value': 'Sometimes'}])
|
||
|
q_food_vegetarian_text = 'Choose your green meal'
|
||
|
self._add_question(self.page_0, q_food_vegetarian_text, 'multiple_choice',
|
||
|
sequence=101,
|
||
|
triggering_answer_ids=[q_is_vegetarian.suggested_answer_ids[0].id,
|
||
|
q_is_vegetarian.suggested_answer_ids[2].id],
|
||
|
survey_id=self.survey.id,
|
||
|
labels=[{'value': 'Vegetarian pizza'}, {'value': 'Vegetarian burger'}])
|
||
|
q_food_not_vegetarian_text = 'Choose your meal in case we serve meet/fish'
|
||
|
self._add_question(self.page_0, q_food_not_vegetarian_text, 'multiple_choice',
|
||
|
sequence=102,
|
||
|
triggering_answer_ids=q_is_vegetarian.suggested_answer_ids[1].ids,
|
||
|
survey_id=self.survey.id,
|
||
|
labels=[{'value': 'Steak with french fries'}, {'value': 'Fish'}])
|
||
|
|
||
|
# Clone the survey
|
||
|
survey_clone = self.survey.copy()
|
||
|
|
||
|
# Verify the conditional layout and that the cloned survey doesn't reference the original survey
|
||
|
q_is_vegetarian_cloned = get_question_by_title(survey_clone, q_is_vegetarian_text)
|
||
|
q_food_vegetarian_cloned = get_question_by_title(survey_clone, q_food_vegetarian_text)
|
||
|
q_food_not_vegetarian_cloned = get_question_by_title(survey_clone, q_food_not_vegetarian_text)
|
||
|
|
||
|
self.assertFalse(bool(q_is_vegetarian_cloned.triggering_answer_ids))
|
||
|
|
||
|
# Vegetarian choice
|
||
|
self.assertTrue(bool(q_food_vegetarian_cloned))
|
||
|
# Correct conditional layout
|
||
|
self.assertEqual(q_food_vegetarian_cloned.triggering_answer_ids.ids,
|
||
|
[q_is_vegetarian_cloned.suggested_answer_ids[0].id, q_is_vegetarian_cloned.suggested_answer_ids[2].id])
|
||
|
# Doesn't reference the original survey
|
||
|
self.assertNotEqual(q_food_vegetarian_cloned.triggering_answer_ids.ids,
|
||
|
[q_is_vegetarian.suggested_answer_ids[0].id, q_is_vegetarian.suggested_answer_ids[2].id])
|
||
|
|
||
|
# Not vegetarian choice
|
||
|
self.assertTrue(bool(q_food_not_vegetarian_cloned.triggering_answer_ids))
|
||
|
# Correct conditional layout
|
||
|
self.assertEqual(q_food_not_vegetarian_cloned.triggering_answer_ids.ids,
|
||
|
q_is_vegetarian_cloned.suggested_answer_ids[1].ids)
|
||
|
# Doesn't reference the original survey
|
||
|
self.assertNotEqual(q_food_not_vegetarian_cloned.triggering_answer_ids.ids,
|
||
|
q_is_vegetarian.suggested_answer_ids[1].ids)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_copy_conditional_question_with_sequence_changed(self):
|
||
|
""" Create a survey with two questions, change the sequence of the questions,
|
||
|
set the second question as conditional on the first one, and check that the conditional
|
||
|
question is still conditional on the first one after copying the survey."""
|
||
|
|
||
|
def get_question_by_title(survey, title):
|
||
|
return survey.question_ids.filtered(lambda q: q.title == title)[0]
|
||
|
|
||
|
# Create the survey questions
|
||
|
q_1 = self._add_question(
|
||
|
self.page_0, 'Q1', 'multiple_choice', survey_id=self.survey.id,
|
||
|
sequence=200, labels=[{'value': 'Yes'}, {'value': 'No'}])
|
||
|
q_2 = self._add_question(
|
||
|
self.page_0, 'Q2', 'multiple_choice', survey_id=self.survey.id,
|
||
|
sequence=300, labels=[{'value': 'Yes'}, {'value': 'No'}])
|
||
|
|
||
|
# Change the sequence of the second question to be before the first one
|
||
|
q_2.write({'sequence': 100})
|
||
|
|
||
|
# Set a conditional question on the first question
|
||
|
q_1.write({'triggering_answer_ids': [Command.set([q_2.suggested_answer_ids[0].id])]})
|
||
|
|
||
|
(q_1 | q_2).invalidate_recordset()
|
||
|
|
||
|
# Clone the survey
|
||
|
cloned_survey = self.survey.copy()
|
||
|
|
||
|
# Check that the sequence of the questions are the same as the original survey
|
||
|
self.assertEqual(get_question_by_title(cloned_survey, 'Q1').sequence, q_1.sequence)
|
||
|
self.assertEqual(get_question_by_title(cloned_survey, 'Q2').sequence, q_2.sequence)
|
||
|
|
||
|
# Check that the conditional question is correctly copied to the right question
|
||
|
self.assertEqual(
|
||
|
get_question_by_title(cloned_survey, 'Q1').triggering_answer_ids[0].value, q_1.triggering_answer_ids[0].value
|
||
|
)
|
||
|
self.assertFalse(bool(get_question_by_title(cloned_survey, 'Q2').triggering_answer_ids))
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_matrix_rows_display_name(self):
|
||
|
"""Check that matrix rows' display name is not changed."""
|
||
|
# A case's shape is: (question title, row value, expected row display names)
|
||
|
cases = [
|
||
|
(
|
||
|
'Question 1',
|
||
|
'Row A is short, so what?',
|
||
|
'Row A is short, so what?',
|
||
|
), (
|
||
|
'Question 2',
|
||
|
'Row B is a very long question, but it is shown by itself so there shouldn\'t be any change',
|
||
|
'Row B is a very long question, but it is shown by itself so there shouldn\'t be any change',
|
||
|
),
|
||
|
]
|
||
|
|
||
|
for question_title, row_value, exp_display_name in cases:
|
||
|
question = self.env['survey.question'].create({
|
||
|
'title': question_title,
|
||
|
'matrix_row_ids': [Command.create({'value': row_value})],
|
||
|
})
|
||
|
|
||
|
with self.subTest(question=question_title, row=row_value):
|
||
|
self.assertEqual(question.matrix_row_ids[0].display_name, exp_display_name)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_suggested_answer_display_name(self):
|
||
|
"""Check that answers' display name is not too long and allows to identify the question & answer.
|
||
|
|
||
|
When a matrix answer though, simply show the value as the question and row should be made
|
||
|
clear via the survey.user.input.line context."""
|
||
|
# A case's shape is: (question title, answer value, expected display name, additional create values)
|
||
|
cases = [
|
||
|
(
|
||
|
'Question 1',
|
||
|
'Answer A is short',
|
||
|
'Question 1 : Answer A is short',
|
||
|
{}
|
||
|
), (
|
||
|
'Question 2',
|
||
|
'Answer B is a very long answer, so it should itself be shortened or we would go too far',
|
||
|
'Question 2 : Answer B is a very long answer, so it should itself be shortened or we...',
|
||
|
{}
|
||
|
), (
|
||
|
'Question 3 is a very long question, so what can we do?',
|
||
|
'Answer A is short',
|
||
|
'Question 3 is a very long question, so what can we do? : Answer A is short',
|
||
|
{}
|
||
|
), (
|
||
|
'Question 4 is a very long question, so what can we do?',
|
||
|
'Answer B is a bit too long for Q4 now',
|
||
|
'Question 4 is a very long question, so what can... : Answer B is a bit too long for Q4 now',
|
||
|
{}
|
||
|
), (
|
||
|
'Question 5 is a very long question, so what can we do?',
|
||
|
'Answer C is so long that both the question and the answer will be shortened',
|
||
|
'Question 5 is a very long... : Answer C is so long that both the question and the...',
|
||
|
{}
|
||
|
), (
|
||
|
'Question 6',
|
||
|
'Answer A is short, so what?',
|
||
|
'Answer A is short, so what?',
|
||
|
{'question_type': 'matrix'},
|
||
|
), (
|
||
|
'Question 7',
|
||
|
'Answer B is a very long answer, but it is shown by itself so there shouldn\'t be any change',
|
||
|
'Answer B is a very long answer, but it is shown by itself so there shouldn\'t be any change',
|
||
|
{'question_type': 'matrix'},
|
||
|
),
|
||
|
]
|
||
|
|
||
|
for question_title, answer_value, exp_display_name, other_values in cases:
|
||
|
question = self.env['survey.question'].create({
|
||
|
'title': question_title,
|
||
|
'suggested_answer_ids': [Command.create({'value': answer_value})],
|
||
|
**other_values
|
||
|
})
|
||
|
|
||
|
with self.subTest(question=question_title, answer=answer_value):
|
||
|
self.assertEqual(question.suggested_answer_ids[0].display_name, exp_display_name)
|
||
|
|
||
|
@users('survey_manager')
|
||
|
def test_unlink_triggers(self):
|
||
|
# Create the survey questions
|
||
|
q_is_vegetarian_text = 'Are you vegetarian?'
|
||
|
q_is_vegetarian = self._add_question(
|
||
|
self.page_0, q_is_vegetarian_text, 'simple_choice', survey_id=self.survey.id, sequence=100,
|
||
|
labels=[{'value': 'Yes'}, {'value': 'No'}, {'value': 'It depends'}], constr_mandatory=True,
|
||
|
)
|
||
|
|
||
|
q_is_kinda_vegetarian_text = 'Would you prefer a veggie meal if possible?'
|
||
|
q_is_kinda_vegetarian = self._add_question(
|
||
|
self.page_0, q_is_kinda_vegetarian_text, 'simple_choice', survey_id=self.survey.id, sequence=101,
|
||
|
labels=[{'value': 'Yes'}, {'value': 'No'}], constr_mandatory=True, triggering_answer_ids=[
|
||
|
Command.link(q_is_vegetarian.suggested_answer_ids[1].id), # It depends
|
||
|
],
|
||
|
)
|
||
|
|
||
|
q_food_vegetarian_text = 'Choose your green meal'
|
||
|
veggie_question = self._add_question(
|
||
|
self.page_0, q_food_vegetarian_text, 'simple_choice', survey_id=self.survey.id, sequence=102,
|
||
|
labels=[{'value': 'Vegetarian pizza'}, {'value': 'Vegetarian burger'}], constr_mandatory=True,
|
||
|
triggering_answer_ids=[
|
||
|
Command.link(q_is_vegetarian.suggested_answer_ids[0].id), # Veggie
|
||
|
Command.link(q_is_kinda_vegetarian.suggested_answer_ids[0].id), # Would prefer veggie
|
||
|
])
|
||
|
|
||
|
q_food_not_vegetarian_text = 'Choose your meal'
|
||
|
not_veggie_question = self._add_question(
|
||
|
self.page_0, q_food_not_vegetarian_text, 'simple_choice', survey_id=self.survey.id, sequence=103,
|
||
|
labels=[{'value': 'Steak with french fries'}, {'value': 'Fish'}], constr_mandatory=True,
|
||
|
triggering_answer_ids=[
|
||
|
Command.link(q_is_vegetarian.suggested_answer_ids[1].id), # Not a veggie
|
||
|
Command.link(q_is_kinda_vegetarian.suggested_answer_ids[1].id), # Would not prefer veggie
|
||
|
],
|
||
|
)
|
||
|
|
||
|
q_is_kinda_vegetarian.unlink()
|
||
|
|
||
|
# Deleting one trigger but maintaining another keeps conditional behavior
|
||
|
self.assertTrue(bool(veggie_question.triggering_answer_ids))
|
||
|
|
||
|
q_is_vegetarian.suggested_answer_ids[0].unlink()
|
||
|
|
||
|
# Deleting answer Yes makes the following question always visible
|
||
|
self.assertFalse(bool(veggie_question.triggering_answer_ids))
|
||
|
|
||
|
# But the other is still conditional
|
||
|
self.assertEqual(not_veggie_question.triggering_answer_ids[0].id, q_is_vegetarian.suggested_answer_ids[0].id)
|
||
|
|
||
|
q_is_vegetarian.unlink()
|
||
|
|
||
|
# Now it will also be always visible
|
||
|
self.assertFalse(bool(not_veggie_question.triggering_answer_ids))
|
||
|
|
||
|
def test_get_correct_answers(self):
|
||
|
questions = self._create_one_question_per_type_with_scoring()
|
||
|
qtype_mapping = {q.question_type: q for q in questions}
|
||
|
expected_correct_answer = {
|
||
|
qtype_mapping['numerical_box'].id: 5,
|
||
|
qtype_mapping['date'].id: '10/16/2023',
|
||
|
qtype_mapping['datetime'].id: '11/17/2023 08:00:00',
|
||
|
qtype_mapping['simple_choice'].id:
|
||
|
qtype_mapping['simple_choice'].suggested_answer_ids.filtered_domain([('value', '=', 'SChoice0')]).ids,
|
||
|
qtype_mapping['multiple_choice'].id:
|
||
|
qtype_mapping['multiple_choice'].suggested_answer_ids.filtered_domain([('value', 'in', ['MChoice0', 'MChoice1'])]).ids,
|
||
|
}
|
||
|
self.assertEqual(questions._get_correct_answers(), expected_correct_answer)
|
||
|
|
||
|
def test_get_pages_and_questions_to_show(self):
|
||
|
"""
|
||
|
Tests the method `_get_pages_and_questions_to_show` - it takes a recordset of
|
||
|
question.question from a survey.survey and returns a recordset without
|
||
|
invalid conditional questions and pages without description
|
||
|
|
||
|
Structure of the test survey:
|
||
|
|
||
|
sequence | type | trigger | validity
|
||
|
----------------------------------------------------------------------
|
||
|
1 | page, no description | / | X
|
||
|
2 | simple_choice | trigger is 5 | X
|
||
|
3 | simple_choice | trigger is 2 | X
|
||
|
4 | page, description | / | V
|
||
|
5 | multiple_choice | / | V
|
||
|
6 | text_box | triggers are 5+7 | V
|
||
|
7 | multiple_choice | | V
|
||
|
"""
|
||
|
|
||
|
my_survey = self.env['survey.survey'].create({
|
||
|
'title': 'my_survey',
|
||
|
'questions_layout': 'page_per_question',
|
||
|
'questions_selection': 'all',
|
||
|
'access_mode': 'public',
|
||
|
})
|
||
|
[
|
||
|
page_without_description,
|
||
|
simple_choice_1,
|
||
|
simple_choice_2,
|
||
|
_page_with_description,
|
||
|
multiple_choice_1,
|
||
|
text_box_2,
|
||
|
multiple_choice_2,
|
||
|
] = self.env['survey.question'].create([{
|
||
|
'title': 'no desc',
|
||
|
'survey_id': my_survey.id,
|
||
|
'sequence': 1,
|
||
|
'question_type': False,
|
||
|
'is_page': True,
|
||
|
'description': False,
|
||
|
}, {
|
||
|
'title': 'simple choice with invalid trigger',
|
||
|
'survey_id': my_survey.id,
|
||
|
'sequence': 2,
|
||
|
'is_page': False,
|
||
|
'question_type': 'simple_choice',
|
||
|
'suggested_answer_ids': [(0, 0, {'value': 'a'})],
|
||
|
}, {
|
||
|
'title': 'simple_choice with chained invalid trigger',
|
||
|
'survey_id': my_survey.id,
|
||
|
'sequence': 3,
|
||
|
'is_page': False,
|
||
|
'question_type': 'simple_choice',
|
||
|
'suggested_answer_ids': [(0, 0, {'value': 'a'})],
|
||
|
}, {
|
||
|
'title': 'with desc',
|
||
|
'survey_id': my_survey.id,
|
||
|
'sequence': 4,
|
||
|
'is_page': True,
|
||
|
'question_type': False,
|
||
|
'description': 'This page has a description',
|
||
|
}, {
|
||
|
'title': 'multiple choice not conditional',
|
||
|
'survey_id': my_survey.id,
|
||
|
'sequence': 5,
|
||
|
'is_page': False,
|
||
|
'question_type': 'multiple_choice',
|
||
|
'suggested_answer_ids': [(0, 0, {'value': 'a'})]
|
||
|
}, {
|
||
|
'title': 'text_box with valid trigger',
|
||
|
'survey_id': my_survey.id,
|
||
|
'sequence': 6,
|
||
|
'is_page': False,
|
||
|
'question_type': 'text_box',
|
||
|
}, {
|
||
|
'title': 'valid multiple_choice',
|
||
|
'survey_id': my_survey.id,
|
||
|
'sequence': 7,
|
||
|
'is_page': False,
|
||
|
'question_type': 'multiple_choice',
|
||
|
'suggested_answer_ids': [(0, 0, {'value': 'a'})]
|
||
|
}])
|
||
|
simple_choice_1.write({'triggering_answer_ids': multiple_choice_1.suggested_answer_ids})
|
||
|
simple_choice_2.write({'triggering_answer_ids': multiple_choice_1.suggested_answer_ids})
|
||
|
text_box_2.write({'triggering_answer_ids': (multiple_choice_1 | multiple_choice_2).suggested_answer_ids})
|
||
|
invalid_records = page_without_description + simple_choice_1 + simple_choice_2
|
||
|
question_and_page_ids = my_survey.question_and_page_ids
|
||
|
returned_questions_and_pages = my_survey._get_pages_and_questions_to_show()
|
||
|
|
||
|
self.assertEqual(question_and_page_ids - invalid_records, returned_questions_and_pages)
|
||
|
|
||
|
def test_notify_subscribers(self):
|
||
|
"""Check that messages are posted only if there are participation followers"""
|
||
|
survey_2 = self.survey.copy()
|
||
|
survey_participation_subtype = self.env.ref('survey.mt_survey_survey_user_input_completed')
|
||
|
user_input_participation_subtype = self.env.ref('survey.mt_survey_user_input_completed')
|
||
|
# Make survey_user (group_survey_user) follow participation to survey (they follow), not survey 2 (no followers)
|
||
|
self.survey.message_subscribe(partner_ids=self.survey_user.partner_id.ids, subtype_ids=survey_participation_subtype.ids)
|
||
|
# Complete a participation for both surveys, only one should trigger a notification for followers
|
||
|
user_inputs = self.env['survey.user_input'].create([{'survey_id': survey.id} for survey in (self.survey, survey_2)])
|
||
|
with self.mock_mail_app():
|
||
|
user_inputs._mark_done()
|
||
|
self.assertEqual(len(self._new_msgs), 1)
|
||
|
self.assertMessageFields(
|
||
|
self._new_msgs,
|
||
|
{
|
||
|
'model': 'survey.user_input',
|
||
|
'subtype_id': user_input_participation_subtype,
|
||
|
'res_id': user_inputs[0].id,
|
||
|
'notified_partner_ids': self.survey_user.partner_id
|
||
|
},
|
||
|
)
|
||
|
|
||
|
def assertAnswerStatus(self, expected_answer_status, questions_statistics):
|
||
|
"""Assert counts for 'Correct', 'Partially', 'Incorrect', 'Unanswered' are 0, and 1 for our expected answer status"""
|
||
|
for status, count in [(total['text'], total['count']) for total in questions_statistics['totals']]:
|
||
|
self.assertEqual(count, 1 if status == expected_answer_status else 0)
|