1, 'field' => 'section', 'name' => '资源对接', 'edit_input' => 'section'], ['id' => 2, 'field' => 'resource', 'name' => '融资', 'edit_input' => 'multi_dimension', 'need_fill' => true, 'dimensions' => [ ['field' => 'intent', 'name' => '意向', 'need_fill' => true, 'select_item' => [['key' => 'yes', 'value' => '是'], ['key' => 'no', 'value' => '否']]], ['field' => 'quantity', 'name' => '数量', 'edit_input' => 'number', 'need_fill' => true, 'config' => ['min' => 0, 'max' => 100, 'integer' => true]], ]], ['id' => 3, 'field' => 'rating', 'name' => '满意度', 'edit_input' => 'radio', 'config' => ['is_score' => true], 'select_item' => [['key' => 'a', 'value' => '满意', 'score' => 5], ['key' => 'b', 'value' => '一般', 'score' => 3]]], ['id' => 4, 'field' => 'needs', 'name' => '需求', 'edit_input' => 'checkbox', 'config' => ['other_key' => 'other', 'other_required' => true], 'select_item' => [['key' => 'bank', 'value' => '银行'], ['key' => 'other', 'value' => '其他']]], ]; } public function testServerSchemaAndZeroArePreserved(): void { $q = $this->questions(); $answers = SurveySchema::normalize($q, [ ['id' => 2, 'name' => '篡改标题', 'value' => ['intent' => 'no', 'quantity' => '0', 'unknown' => 8]], ['id' => 3, 'value' => 'a', 'select_item' => [['key' => 'a', 'score' => 999]]], ], true); $this->assertSame('融资', $answers[1]['name']); $this->assertSame(['intent' => 'no', 'quantity' => 0.0], $answers[1]['value']); $this->assertSame(5, $answers[2]['select_item'][0]['score']); $this->assertSame([], $answers[3]['value']); $reply = SurveySchema::replyQuestions([], $answers, ['version' => 2, 'questions' => $q]); $this->assertSame('0', $reply[1]['dimensions'][1]['display_value']); $this->assertSame('否', $reply[1]['dimensions'][0]['display_value']); } public function testReplySelectionsSupportSnapshotsLegacyAndZeroKeys(): void { $q = $this->questions(); $q[2]['select_item'][] = ['key' => 0, 'value' => '零分']; $reply = SurveySchema::replyQuestions([], [ ['id' => 2, 'value' => ['intent' => 'no']], ['id' => 3, 'value' => 0], ['id' => 4, 'value' => ['bank', 'other']], ], ['version' => 2, 'questions' => $q]); $this->assertSame(['no'], $reply[1]['dimensions'][0]['selected_keys']); $this->assertSame([], $reply[1]['dimensions'][1]['selected_keys']); $this->assertSame(['0'], $reply[2]['selected_keys']); $this->assertSame(['bank', 'other'], $reply[3]['selected_keys']); $legacy = SurveySchema::replyQuestions($q, [ ['id' => 2, 'value' => ['intent' => '否']], ['id' => 3, 'value' => '满意'], ['id' => 4, 'value' => '银行,其他'], ], null); $this->assertSame(['no'], $legacy[1]['dimensions'][0]['selected_keys']); $this->assertSame(['a'], $legacy[2]['selected_keys']); $this->assertSame(['bank', 'other'], $legacy[3]['selected_keys']); $empty = SurveySchema::replyQuestions($q, [], null); $this->assertSame([], $empty[2]['selected_keys']); } public function testCustomScoresAreNotLimitedToFivePointScale(): void { $q = $this->questions()[2]; $q['select_item'] = [['key' => 'custom', 'value' => '自定义', 'score' => -2.5], ['key' => 'ten', 'value' => '10分', 'score' => 10]]; $q = SurveySchema::question($q); $this->assertSame(-2.5, $q['select_item'][0]['score']); $this->assertSame(10.0, $q['select_item'][1]['score']); } public function testCombinationRequiredIsControlledByParent(): void { $q = $this->questions()[1]; $q['need_fill'] = false; $q['rule'] = ''; $normalized = SurveySchema::normalize([$q], [], true); $this->assertNull($normalized[0]['value']['intent']); $q['need_fill'] = true; $this->expectException(InvalidArgumentException::class); SurveySchema::normalize([$q], [['id' => 2, 'value' => ['intent' => 'yes']]], true); } /** @dataProvider invalidNumberProvider */ public function testInvalidNumbersAreRejected($value): void { $this->expectException(InvalidArgumentException::class); SurveySchema::normalize($this->questions(), [['id' => 2, 'value' => ['intent' => 'yes', 'quantity' => $value]]], true); } public function invalidNumberProvider(): array { return [[-1], [1.5], [101], ['abc'], [null], ['1e999']]; } public function testOtherRequiresTextAndUnselectedTextIsDiscarded(): void { $q = [$this->questions()[3]]; $answer = SurveySchema::normalize($q, [['id' => 4, 'value' => ['bank'], 'other_text' => '旧内容']], true); $this->assertSame('', $answer[0]['other_text']); $this->expectException(InvalidArgumentException::class); SurveySchema::normalize($q, [['id' => 4, 'value' => ['other']]], true); } public function testForgedOptionsCannotBeSubmitted(): void { $this->expectException(InvalidArgumentException::class); SurveySchema::normalize([$this->questions()[2]], [['id' => 3, 'value' => 'fake']], true); } public function testLegacyTextOptionsAndSerializedDimensions(): void { $q = $this->questions(); $answers = [['id' => 2, 'value' => json_encode(['intent' => '否', 'quantity' => 0])], ['id' => 3, 'value' => '满意']]; $normalized = SurveySchema::normalize($q, $answers); $this->assertSame('no', $normalized[1]['value']['intent']); $this->assertSame('a', $normalized[2]['value']); $reply = SurveySchema::replyQuestions($q, $answers, null); $this->assertSame('否', $reply[1]['dimensions'][0]['display_value']); $this->assertSame('0', $reply[1]['dimensions'][1]['display_value']); } public function testStatisticsUseValidAnswersAndCountSelectionsOnce(): void { $q = $this->questions(); $stats = SurveyStatistics::summarize($q, [ ['snapshot' => ['version' => 2], 'data' => [['id' => 2, 'value' => ['intent' => 'no', 'quantity' => 0]], ['id' => 3, 'value' => 'a'], ['id' => 4, 'value' => ['bank', 'other', 'bank'], 'other_text' => '融资']]], ['data' => [['id' => 2, 'value' => '{"intent":"是","quantity":3}'], ['id' => 3, 'value' => '一般']]], ['data' => []], ]); $this->assertSame(3, $stats['responses']); $this->assertSame(3, $stats['questions_count']); $this->assertEquals(4, $stats['average']); $this->assertSame(2, $stats['questions'][1]['dimensions'][1]['stats']['valid_count']); $this->assertEquals(3, $stats['questions'][1]['dimensions'][1]['stats']['sum']); $this->assertEquals(100, $stats['questions'][3]['stats']['options'][0]['percentage']); $this->assertSame(1, $stats['questions'][3]['stats']['options'][0]['count']); $this->assertSame(1, $stats['questions'][3]['stats']['other_count']); } public function testSnapshotSurvivesChangedCurrentDefinitions(): void { $original = $this->questions()[2]; $current = array_replace($original, ['name' => '新问题']); $reply = SurveySchema::replyQuestions([$current], [['id' => 3, 'value' => 'a']], ['version' => 2, 'questions' => [$original]]); $this->assertSame('满意度', $reply[0]['name']); $this->assertSame('满意', $reply[0]['display_value']); } public function testQuestionValidationRejectsDuplicateDimensionIdentifiers(): void { $q = $this->questions()[1]; $q['dimensions'][1]['field'] = 'intent'; $this->expectException(InvalidArgumentException::class); SurveySchema::question($q); } public function testDeletedLegacyQuestionRemainsVisible(): void { $q = $this->questions()[2]; $q['value'] = '满意'; $reply = SurveySchema::replyQuestions([], [$q], null); $this->assertCount(1, $reply); $this->assertSame('满意', $reply[0]['display_value']); } }