mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-23 11:12:00 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
|
|
@ -1,14 +1,57 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
|
||||
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):
|
||||
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
|
||||
|
|
@ -44,6 +87,7 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
self.assertEqual(fourth_attempt['attempts_count'], 4)
|
||||
|
||||
@freeze_time("2020-02-15 18:00")
|
||||
@users('survey_manager')
|
||||
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.
|
||||
|
|
@ -67,7 +111,7 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
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')
|
||||
self.assertEqual(question_answer.display_name, '2020-02-15 19: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')
|
||||
|
|
@ -83,6 +127,9 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
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')
|
||||
elif question.question_type == 'scale':
|
||||
question_answer = self._add_answer_line(question, user_input, '3')
|
||||
self.assertEqual(question_answer.display_name, '3')
|
||||
|
||||
@users('survey_manager')
|
||||
def test_answer_validation_mandatory(self):
|
||||
|
|
@ -100,7 +147,7 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
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.validate_question('Is Alfred an answer?'),
|
||||
{question.id: _('This is not a date')}
|
||||
)
|
||||
|
||||
|
|
@ -126,7 +173,7 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
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.validate_question('Is Alfred an answer?'),
|
||||
{question.id: _('This is not a number')}
|
||||
)
|
||||
|
||||
|
|
@ -180,6 +227,86 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
{}
|
||||
)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_simple_choice_validation_multiple_answers(self):
|
||||
"""
|
||||
Check that a 'simple_choice' question fails validation if more than one
|
||||
valid answer is provided.
|
||||
"""
|
||||
question = self._add_question(
|
||||
self.page_0, 'Simple Choice Constraint Test', 'simple_choice',
|
||||
constr_mandatory=True,
|
||||
comments_allowed=True,
|
||||
comment_count_as_answer=True,
|
||||
labels=[{'value': 'Choice X'}, {'value': 'Choice Y'}]
|
||||
)
|
||||
answer_choice_x_id = question.suggested_answer_ids[0].id
|
||||
answer_choice_y_id = question.suggested_answer_ids[1].id
|
||||
|
||||
scenarios = [
|
||||
(
|
||||
'Two selected choices should not be allowed',
|
||||
[answer_choice_x_id, answer_choice_y_id],
|
||||
None,
|
||||
True,
|
||||
),
|
||||
(
|
||||
'One choice and one comment that counts as an answer',
|
||||
answer_choice_x_id,
|
||||
'This is my comment, which is also an answer.',
|
||||
True,
|
||||
),
|
||||
(
|
||||
'A single valid answer should pass validation',
|
||||
answer_choice_x_id,
|
||||
None,
|
||||
False,
|
||||
),
|
||||
(
|
||||
'A single valid comment should pass validation',
|
||||
'',
|
||||
'This is my comment, which is also an answer.',
|
||||
False,
|
||||
),
|
||||
]
|
||||
|
||||
for case_description, answers, comment, is_multiple_answers in scenarios:
|
||||
with self.subTest(answers=answers, comment=comment):
|
||||
self.assertEqual(
|
||||
question.validate_question(answers, comment),
|
||||
{question.id: 'For this question, you can only select one answer.'} if is_multiple_answers else {},
|
||||
case_description,
|
||||
)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_answer_validation_comment(self):
|
||||
""" Check that a comment validates a mandatory question based on 'comment_count_as_answer'. """
|
||||
# Scenario 1: A comment counts as a valid answer.
|
||||
question_ok = self._add_question(
|
||||
self.page_0, 'Q_OK', 'multiple_choice',
|
||||
constr_mandatory=True, validation_error_msg='ValidationError',
|
||||
comments_allowed=True,
|
||||
comment_count_as_answer=True,
|
||||
labels=[{'value': 'Choice A'}, {'value': 'Choice B'}])
|
||||
|
||||
self.assertEqual(
|
||||
question_ok.validate_question(answer='', comment='This comment is a valid answer.'),
|
||||
{}
|
||||
)
|
||||
|
||||
# Scenario 2: A comment does NOT count as a valid answer.
|
||||
question_fail = self._add_question(
|
||||
self.page_0, 'Q_FAIL', 'multiple_choice',
|
||||
constr_mandatory=True, validation_error_msg='ValidationError',
|
||||
comments_allowed=True,
|
||||
comment_count_as_answer=False,
|
||||
labels=[{'value': 'Choice A'}, {'value': 'Choice B'}])
|
||||
|
||||
self.assertEqual(
|
||||
question_fail.validate_question(answer='', comment='This comment is not enough.'),
|
||||
{question_fail.id: 'TestError'}
|
||||
)
|
||||
|
||||
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. """
|
||||
|
|
@ -220,6 +347,83 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
self.assertEqual(user_input.scoring_percentage, 100)
|
||||
self.assertTrue(user_input.scoring_success)
|
||||
|
||||
def test_session_code_generation(self):
|
||||
surveys = self.env['survey.survey'].create([{
|
||||
'title': f'Survey {i}'
|
||||
} for i in range(30)])
|
||||
survey_codes = surveys.mapped('session_code')
|
||||
self.assertEqual(len(survey_codes), 30)
|
||||
for code in survey_codes:
|
||||
self.assertTrue(bool(code))
|
||||
self.assertEqual(
|
||||
len(surveys.filtered(lambda survey: survey.session_code == code)),
|
||||
1,
|
||||
f"Each code should be unique, found multiple occurrences of: {code}"
|
||||
)
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
def assert_answer_status(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)
|
||||
|
||||
# this answer is incorrect with no score: should be considered as incorrect
|
||||
statistics = user_input._prepare_statistics()[user_input]
|
||||
assert_answer_status('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]
|
||||
assert_answer_status('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]
|
||||
assert_answer_status('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]
|
||||
assert_answer_status('Partially', statistics)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_skipped_values(self):
|
||||
""" Create one question per type of questions.
|
||||
|
|
@ -231,11 +435,35 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
|
||||
for question in questions:
|
||||
answer = '' if question.question_type in ['char_box', 'text_box'] else None
|
||||
survey_user.save_lines(question, answer)
|
||||
survey_user._save_lines(question, answer)
|
||||
|
||||
for question in questions:
|
||||
self._assert_skipped_question(question, survey_user)
|
||||
|
||||
@users('survey_manager')
|
||||
def test_multiple_choice_comment_not_skipped(self):
|
||||
""" Test that a multiple choice question with only a comment is not marked as skipped. """
|
||||
survey_user = self.survey._create_answer(user=self.survey_user)
|
||||
question = self._add_question(
|
||||
self.page_0, 'MCQ with Comment', 'multiple_choice',
|
||||
comments_allowed=True,
|
||||
comment_count_as_answer=True,
|
||||
labels=[{'value': 'Choice A'}, {'value': 'Choice B'}]
|
||||
)
|
||||
|
||||
# Save an answer with no selected choice but with a comment.
|
||||
survey_user._save_lines(question, answer=[], comment='This is only a comment')
|
||||
|
||||
answer_line = self.env['survey.user_input.line'].search([
|
||||
('user_input_id', '=', survey_user.id),
|
||||
('question_id', '=', question.id)
|
||||
])
|
||||
|
||||
self.assertEqual(len(answer_line), 1)
|
||||
self.assertFalse(answer_line.skipped)
|
||||
self.assertEqual(answer_line.answer_type, 'char_box')
|
||||
self.assertEqual(answer_line.value_char_box, 'This is only a comment')
|
||||
|
||||
@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
|
||||
|
|
@ -246,22 +474,21 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
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_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'}])
|
||||
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',
|
||||
is_conditional=True, sequence=101,
|
||||
triggering_question_id=q_is_vegetarian.id,
|
||||
triggering_answer_id=q_is_vegetarian.suggested_answer_ids[0].id,
|
||||
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'
|
||||
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',
|
||||
is_conditional=True, sequence=102,
|
||||
triggering_question_id=q_is_vegetarian.id,
|
||||
triggering_answer_id=q_is_vegetarian.suggested_answer_ids[1].id,
|
||||
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'}])
|
||||
|
||||
|
|
@ -273,29 +500,25 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
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(q_is_vegetarian_cloned.is_conditional)
|
||||
self.assertFalse(bool(q_is_vegetarian_cloned.triggering_answer_ids))
|
||||
|
||||
# Vegetarian choice
|
||||
self.assertTrue(q_food_vegetarian_cloned)
|
||||
self.assertTrue(bool(q_food_vegetarian_cloned))
|
||||
# Correct conditional layout
|
||||
self.assertEqual(q_food_vegetarian_cloned.triggering_question_id.id, q_is_vegetarian_cloned.id)
|
||||
self.assertEqual(q_food_vegetarian_cloned.triggering_answer_id.id,
|
||||
q_is_vegetarian_cloned.suggested_answer_ids[0].id)
|
||||
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_question_id.id, q_is_vegetarian.id)
|
||||
self.assertNotEqual(q_food_vegetarian_cloned.triggering_answer_id.id,
|
||||
q_is_vegetarian.suggested_answer_ids[0].id)
|
||||
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(q_food_not_vegetarian_cloned.is_conditional)
|
||||
self.assertTrue(bool(q_food_not_vegetarian_cloned.triggering_answer_ids))
|
||||
# Correct conditional layout
|
||||
self.assertEqual(q_food_not_vegetarian_cloned.triggering_question_id.id, q_is_vegetarian_cloned.id)
|
||||
self.assertEqual(q_food_not_vegetarian_cloned.triggering_answer_id.id,
|
||||
q_is_vegetarian_cloned.suggested_answer_ids[1].id)
|
||||
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_question_id.id, q_is_vegetarian.id)
|
||||
self.assertNotEqual(q_food_not_vegetarian_cloned.triggering_answer_id.id,
|
||||
q_is_vegetarian.suggested_answer_ids[1].id)
|
||||
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):
|
||||
|
|
@ -318,11 +541,7 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
q_2.write({'sequence': 100})
|
||||
|
||||
# Set a conditional question on the first question
|
||||
q_1.write({
|
||||
'is_conditional': True,
|
||||
'triggering_question_id': q_2.id,
|
||||
'triggering_answer_id': q_2.suggested_answer_ids[0].id,
|
||||
})
|
||||
q_1.write({'triggering_answer_ids': [Command.set([q_2.suggested_answer_ids[0].id])]})
|
||||
|
||||
(q_1 | q_2).invalidate_recordset()
|
||||
|
||||
|
|
@ -334,8 +553,159 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
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_question_id.title, q_1.triggering_question_id.title)
|
||||
self.assertFalse(get_question_by_title(cloned_survey, 'Q2').triggering_question_id)
|
||||
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 AM',
|
||||
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):
|
||||
"""
|
||||
|
|
@ -345,20 +715,15 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
|
||||
Structure of the test survey:
|
||||
|
||||
sequence | type | trigger | validity
|
||||
sequence | type | trigger | validity
|
||||
----------------------------------------------------------------------
|
||||
1 | page, no description | / | X
|
||||
2 | text_box | trigger is 6 | X
|
||||
3 | numerical_box | trigger is 2 | X
|
||||
4 | simple_choice | / | V
|
||||
5 | page, description | / | V
|
||||
6 | multiple_choice | / | V
|
||||
7 | multiple_choice, no answers | / | V
|
||||
8 | text_box | trigger is 6 | V
|
||||
9 | matrix | trigger is 5 | X
|
||||
10 | simple_choice | trigger is 7 | X
|
||||
11 | simple_choice, no answers | trigger is 8 | X
|
||||
12 | text_box | trigger is 11 | X
|
||||
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({
|
||||
|
|
@ -369,17 +734,12 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
})
|
||||
[
|
||||
page_without_description,
|
||||
text_box_1,
|
||||
numerical_box,
|
||||
_simple_choice_1,
|
||||
page_with_description,
|
||||
multiple_choice_1,
|
||||
multiple_choice_2,
|
||||
text_box_2,
|
||||
matrix,
|
||||
simple_choice_1,
|
||||
simple_choice_2,
|
||||
simple_choice_3,
|
||||
text_box_3,
|
||||
_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,
|
||||
|
|
@ -388,86 +748,51 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
'is_page': True,
|
||||
'description': False,
|
||||
}, {
|
||||
'title': 'text_box with invalid trigger',
|
||||
'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': 'numerical box with trigger that is invalid',
|
||||
'title': 'simple_choice with chained invalid trigger',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 3,
|
||||
'is_page': False,
|
||||
'question_type': 'numerical_box',
|
||||
}, {
|
||||
'title': 'valid simple_choice',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 4,
|
||||
'is_page': False,
|
||||
'question_type': 'simple_choice',
|
||||
'suggested_answer_ids': [(0, 0, {'value': 'a'})],
|
||||
}, {
|
||||
'title': 'with desc',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 5,
|
||||
'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': 6,
|
||||
'sequence': 5,
|
||||
'is_page': False,
|
||||
'question_type': 'multiple_choice',
|
||||
'suggested_answer_ids': [(0, 0, {'value': 'a'})]
|
||||
}, {
|
||||
'title': 'multiple_choice with no answers',
|
||||
'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',
|
||||
}, {
|
||||
'title': 'text_box with valid trigger',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 8,
|
||||
'is_page': False,
|
||||
'question_type': 'text_box',
|
||||
}, {
|
||||
'title': 'matrix with invalid trigger (page)',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 9,
|
||||
'is_page': False,
|
||||
'question_type': 'matrix',
|
||||
}, {
|
||||
'title': 'simple choice w/ invalid trigger (no suggested_answer_ids)',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 10,
|
||||
'is_page': False,
|
||||
'question_type': 'simple_choice',
|
||||
}, {
|
||||
'title': 'text_box w/ invalid trigger (not a mcq)',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 11,
|
||||
'is_page': False,
|
||||
'question_type': 'simple_choice',
|
||||
'suggested_answer_ids': False,
|
||||
}, {
|
||||
'title': 'text_box w/ invalid trigger (suggested_answer_ids is False)',
|
||||
'survey_id': my_survey.id,
|
||||
'sequence': 12,
|
||||
'is_page': False,
|
||||
'question_type': 'text_box',
|
||||
'suggested_answer_ids': [(0, 0, {'value': 'a'})]
|
||||
}])
|
||||
text_box_1.write({'is_conditional': True, 'triggering_question_id': multiple_choice_1.id})
|
||||
numerical_box.write({'is_conditional': True, 'triggering_question_id': text_box_1.id})
|
||||
text_box_2.write({'is_conditional': True, 'triggering_question_id': multiple_choice_1.id})
|
||||
matrix.write({'is_conditional': True, 'triggering_question_id': page_with_description.id})
|
||||
simple_choice_2.write({'is_conditional': True, 'triggering_question_id': multiple_choice_2.id})
|
||||
simple_choice_3.write({'is_conditional': True, 'triggering_question_id': text_box_2.id})
|
||||
text_box_3.write({'is_conditional': True, 'triggering_question_id': simple_choice_3.id})
|
||||
|
||||
invalid_records = page_without_description + text_box_1 + numerical_box \
|
||||
+ matrix + simple_choice_2 + simple_choice_3 + text_box_3
|
||||
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()
|
||||
|
||||
|
|
@ -475,7 +800,7 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
|
||||
def test_survey_session_leaderboard(self):
|
||||
"""Check leaderboard rendering with small (max) scores values."""
|
||||
start_time = fields.datetime(2023, 7, 7, 12, 0, 0)
|
||||
start_time = datetime.datetime(2023, 7, 7, 12, 0, 0)
|
||||
test_survey = self.env['survey.survey'].create({
|
||||
'title': 'Test This Survey',
|
||||
'scoring_type': 'scoring_with_answers',
|
||||
|
|
@ -507,3 +832,177 @@ class TestSurveyInternals(common.TestSurveyCommon):
|
|||
'animate': True,
|
||||
'leaderboard': test_survey._prepare_leaderboard_values()
|
||||
})
|
||||
|
||||
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 test_survey_session_speed_reward_config_propagation(self):
|
||||
"""Check the speed rating time limit propagation to non time-customized questions."""
|
||||
test_survey = self.env['survey.survey'].create({
|
||||
'title': 'Test This Survey',
|
||||
'scoring_type': 'scoring_with_answers',
|
||||
'question_and_page_ids': [
|
||||
Command.create({
|
||||
'is_time_customized': True,
|
||||
'is_time_limited': True,
|
||||
'time_limit': 30,
|
||||
'title': 'Question A',
|
||||
}), Command.create({
|
||||
'is_time_customized': True,
|
||||
'is_time_limited': True,
|
||||
'time_limit': 40,
|
||||
'title': 'Question B',
|
||||
}), Command.create({
|
||||
'time_limit': 11, # left-over somehow
|
||||
'title': 'Question C',
|
||||
}),
|
||||
],
|
||||
})
|
||||
self.assertFalse(test_survey.session_speed_rating)
|
||||
|
||||
test_survey.write({'session_speed_rating': True, 'session_speed_rating_time_limit': 30})
|
||||
self.assertEqual(test_survey.session_speed_rating_time_limit, 30)
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('is_time_limited')}, {True})
|
||||
self.assertListEqual(test_survey.question_ids.mapped('time_limit'), [30, 40, 30])
|
||||
self.assertListEqual(test_survey.question_ids.mapped('is_time_customized'), [False, True, False])
|
||||
|
||||
test_survey.session_speed_rating_time_limit = 40
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('time_limit')}, {40})
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('is_time_customized')}, {False})
|
||||
|
||||
test_survey.question_ids[:2].write({
|
||||
"is_time_limited": False,
|
||||
'is_time_customized': True, # As would the client do
|
||||
})
|
||||
self.assertListEqual(test_survey.question_ids.mapped('is_time_limited'), [False, False, True])
|
||||
self.assertListEqual(test_survey.question_ids.mapped('is_time_customized'), [True, True, False])
|
||||
|
||||
test_survey.session_speed_rating_time_limit = 20
|
||||
self.assertListEqual(test_survey.question_ids.mapped('is_time_limited'), [False, False, True])
|
||||
self.assertListEqual(test_survey.question_ids.mapped('is_time_customized'), [True, True, False])
|
||||
self.assertEqual(test_survey.question_ids[2].time_limit, 20)
|
||||
|
||||
test_survey.session_speed_rating = False
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('is_time_limited')}, {False})
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('is_time_customized')}, {False})
|
||||
|
||||
# test update in batch
|
||||
test_survey.write({'session_speed_rating': True, 'session_speed_rating_time_limit': 30})
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('is_time_limited')}, {True})
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('time_limit')}, {30})
|
||||
self.assertSetEqual({*test_survey.question_ids.mapped('is_time_customized')}, {False})
|
||||
|
||||
def test_survey_session_speed_reward_default_applied(self):
|
||||
"""Check that new questions added to a survey with speed reward will apply defaults."""
|
||||
test_survey = self.env['survey.survey'].create({
|
||||
'title': 'Test This Survey',
|
||||
'scoring_type': 'scoring_with_answers',
|
||||
'session_speed_rating': True,
|
||||
'session_speed_rating_time_limit': 60,
|
||||
})
|
||||
question_1, question_2, question_3, question_4 = self.env['survey.question'].create([{
|
||||
'is_time_limited': True, # from client, unedited time limits (from default_get)
|
||||
'question_type': 'numerical_box',
|
||||
'survey_id': test_survey.id,
|
||||
'time_limit': 60,
|
||||
'title': 'Question 1',
|
||||
}, {
|
||||
'survey_id': test_survey.id, # simple values (via rpc for example), will be updated to is_time_customized
|
||||
'question_type': 'numerical_box',
|
||||
'title': 'Question 2',
|
||||
}, {
|
||||
'is_time_customized': True,
|
||||
'is_time_limited': False,
|
||||
'question_type': 'numerical_box',
|
||||
'survey_id': test_survey.id,
|
||||
'title': 'Question 3',
|
||||
}, {
|
||||
'is_time_customized': True, # override in client
|
||||
'is_time_limited': True,
|
||||
'question_type': 'numerical_box',
|
||||
'survey_id': test_survey.id,
|
||||
'time_limit': 30,
|
||||
'title': 'Question 4',
|
||||
},
|
||||
])
|
||||
self.assertTrue(question_1.is_time_limited)
|
||||
self.assertEqual(question_1.time_limit, 60)
|
||||
self.assertFalse(question_1.is_time_customized)
|
||||
|
||||
self.assertFalse(question_2.is_time_limited)
|
||||
self.assertFalse(question_2.time_limit)
|
||||
self.assertTrue(question_2.is_time_customized)
|
||||
|
||||
self.assertFalse(question_3.is_time_limited)
|
||||
self.assertTrue(question_3.is_time_customized)
|
||||
self.assertFalse(question_2.time_limit)
|
||||
|
||||
self.assertTrue(question_4.is_time_limited)
|
||||
self.assertEqual(question_4.time_limit, 30)
|
||||
self.assertTrue(question_4.is_time_customized)
|
||||
|
||||
def test_survey_time_limits_results(self):
|
||||
"""Check that speed-related scores awarded are correctly computed."""
|
||||
start_time = datetime.datetime(2023, 7, 7, 12, 0, 0)
|
||||
test_survey = self.env['survey.survey'].create({
|
||||
'title': 'Test This Survey',
|
||||
'scoring_type': 'scoring_with_answers',
|
||||
'scoring_success_min': 80.0,
|
||||
'session_speed_rating': True,
|
||||
'session_speed_rating_time_limit': 30,
|
||||
'session_question_start_time': start_time,
|
||||
})
|
||||
q_01 = self.env['survey.question'].create([{
|
||||
'is_time_customized': True,
|
||||
'is_time_limited': True,
|
||||
'question_type': 'simple_choice',
|
||||
'suggested_answer_ids': [
|
||||
Command.create({'value': 'In Asia', 'answer_score': 5.0, 'is_correct': True}),
|
||||
Command.create({'value': 'In Europe', 'answer_score': 0., 'is_correct': False}),
|
||||
],
|
||||
'survey_id': test_survey.id,
|
||||
'time_limit': 60,
|
||||
'title': 'Where is india?',
|
||||
}])
|
||||
test_survey.session_question_id = q_01
|
||||
|
||||
answer_correct, answer_incorrect = q_01.suggested_answer_ids
|
||||
user_input = self.env['survey.user_input'].create({'survey_id': test_survey.id, 'is_session_answer': True})
|
||||
for (seconds_since_start, answer), expected_score in zip(
|
||||
[
|
||||
(61, answer_correct), # time limit elapsed
|
||||
(61, answer_incorrect),
|
||||
(31, answer_correct), # half of time limit elapsed
|
||||
(31, answer_incorrect),
|
||||
(2, answer_correct), # end of max_score_delay
|
||||
(2, answer_incorrect),
|
||||
], [2.5, 0.0, 3.75, 0.0, 5.0, 0.0], # 2.5 if succeeded + up to 2.5 depending on time to answer
|
||||
):
|
||||
with (self.subTest(elapsed=seconds_since_start, is_correct=answer.is_correct),
|
||||
freeze_time(start_time + datetime.timedelta(seconds=seconds_since_start))):
|
||||
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': answer.id,
|
||||
})
|
||||
self.assertEqual(user_input_line.answer_score, expected_score)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue