diff --git a/tests/phpunit/entries/test_FrmEntryMeta.php b/tests/phpunit/entries/test_FrmEntryMeta.php index 3a4387262f..a83e1db564 100644 --- a/tests/phpunit/entries/test_FrmEntryMeta.php +++ b/tests/phpunit/entries/test_FrmEntryMeta.php @@ -53,6 +53,159 @@ public function test_update_entry_metas() { $this->assertNull( $meta ); } + /** + * A text field leaves a non-numeric value alone while a number field coerces it to a float, + * so the pair tells us which field packed a given value. + * + * @return array The form, the text field id and the number field id. + */ + private function create_text_and_number_fields() { + $form = $this->factory->form->create_and_get(); + + $text_field_id = $this->factory->field->create( + array( + 'form_id' => $form->id, + 'type' => 'text', + 'field_key' => 'meta_text_' . $form->id, + ) + ); + + $number_field_id = $this->factory->field->create( + array( + 'form_id' => $form->id, + 'type' => 'number', + 'field_key' => 'meta_number_' . $form->id, + ) + ); + + return array( $form, $text_field_id, $number_field_id ); + } + + /** + * The field argument has to stay optional. add_entry_meta() and update_entry_meta() are + * called with four arguments from dozens of places across the add-ons, and those callers + * ship on their own release cycles. + * + * @covers FrmEntryMeta::add_entry_meta + * @covers FrmEntryMeta::update_entry_meta + */ + public function test_entry_meta_writers_still_accept_four_arguments() { + foreach ( array( 'add_entry_meta', 'update_entry_meta' ) as $method ) { + $reflection = new ReflectionMethod( 'FrmEntryMeta', $method ); + $this->assertSame( 4, $reflection->getNumberOfRequiredParameters(), $method . '() should keep exactly four required parameters.' ); + } + + list( $form, $text_field_id ) = $this->create_text_and_number_fields(); + + $entry_id = $this->factory->entry->create( $this->factory->field->generate_entry_array( $form ) ); + + FrmEntryMeta::update_entry_meta( $entry_id, $text_field_id, '', 'Updated with four arguments' ); + + $stored = FrmEntryMeta::get_entry_meta_by_field( $entry_id, $text_field_id ); + $this->assertSame( 'Updated with four arguments', $stored, 'A four argument update should still store the value.' ); + } + + /** + * The loop in update_entry_metas() hands each writer the field it loaded for that value. + * Packing two field types in one call proves each value is packed with its own field, rather + * than one iteration's field leaking into the next. + * + * @covers FrmEntryMeta::update_entry_metas + */ + public function test_update_entry_metas_packs_each_existing_value_with_its_own_field() { + list( $form, $text_field_id, $number_field_id ) = $this->create_text_and_number_fields(); + + $entry_id = $this->factory->entry->create( $this->factory->field->generate_entry_array( $form ) ); + + FrmEntryMeta::update_entry_metas( + $entry_id, + array( + $text_field_id => 'abc', + $number_field_id => 'abc', + ) + ); + + $this->assertSame( 'abc', FrmEntryMeta::get_entry_meta_by_field( $entry_id, $text_field_id ), 'The text field should keep a non-numeric value as typed.' ); + $this->assertSame( '0', FrmEntryMeta::get_entry_meta_by_field( $entry_id, $number_field_id ), 'The number field should coerce a non-numeric value to zero.' ); + } + + /** + * The same has to hold on the insert path, which runs for any field that has no row on the + * entry yet. That is every field on a later page of a multi-page form. + * + * @covers FrmEntryMeta::update_entry_metas + */ + public function test_update_entry_metas_packs_each_new_value_with_its_own_field() { + $form = $this->factory->form->create_and_get(); + + $this->factory->field->create( + array( + 'form_id' => $form->id, + 'type' => 'text', + 'field_key' => 'meta_seed_' . $form->id, + ) + ); + + // Created before the fields below, so neither of them has a row on this entry yet. + $entry_id = $this->factory->entry->create( $this->factory->field->generate_entry_array( $form ) ); + + $text_field_id = $this->factory->field->create( + array( + 'form_id' => $form->id, + 'type' => 'text', + 'field_key' => 'meta_new_text_' . $form->id, + ) + ); + + $number_field_id = $this->factory->field->create( + array( + 'form_id' => $form->id, + 'type' => 'number', + 'field_key' => 'meta_new_number_' . $form->id, + ) + ); + + $this->assertNull( FrmEntryMeta::get_entry_meta_by_field( $entry_id, $number_field_id ), 'The number field should have no row before the update.' ); + + FrmEntryMeta::update_entry_metas( + $entry_id, + array( + $text_field_id => 'abc', + $number_field_id => 'abc', + ) + ); + + $this->assertSame( 'abc', FrmEntryMeta::get_entry_meta_by_field( $entry_id, $text_field_id ), 'A newly inserted text value should be stored as typed.' ); + $this->assertSame( '0', FrmEntryMeta::get_entry_meta_by_field( $entry_id, $number_field_id ), 'A newly inserted number value should be coerced to zero.' ); + } + + /** + * Values can be keyed by field key instead of field id, in which case the field handed to the + * writers is the one resolved from that key. A mix of both keying styles in one call has to + * still pack every value with the right field. + * + * @covers FrmEntryMeta::update_entry_metas + */ + public function test_update_entry_metas_packs_values_keyed_by_field_key_with_the_resolved_field() { + list( $form, $text_field_id, $number_field_id ) = $this->create_text_and_number_fields(); + + $entry_id = $this->factory->entry->create( $this->factory->field->generate_entry_array( $form ) ); + + FrmEntryMeta::update_entry_metas( + $entry_id, + array( + FrmField::get_key_by_id( $text_field_id ) => 'abc', + $number_field_id => 'abc', + ) + ); + + $stored_text = FrmEntryMeta::get_entry_meta_by_field( $entry_id, $text_field_id ); + $stored_number = FrmEntryMeta::get_entry_meta_by_field( $entry_id, $number_field_id ); + + $this->assertSame( 'abc', $stored_text, 'A value keyed by field key should be packed with the text field it resolves to.' ); + $this->assertSame( '0', $stored_number, 'A value keyed by field id alongside it should still be packed with the number field.' ); + } + /** * @covers FrmEntryMeta::should_join_fields_table */