Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .phpunit.result.cache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":1,"defects":{"Gedcomx\\Unit\\GedcomxFileTests::testCreateGedxFile":5},"times":{"Gedcomx\\Tests\\Unit\\Agent\\AddressTests::testAddressDeserialization":0.012,"Gedcomx\\Tests\\Unit\\Agent\\AddressTests::testAddressGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Agent\\AddressTests::testAddressWithMinimalData":0,"Gedcomx\\Tests\\Unit\\Agent\\AgentTests::testAgentDeserialization":0.002,"Gedcomx\\Tests\\Unit\\Agent\\AgentTests::testAgentGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Agent\\AgentTests::testAgentWithoutNames":0,"Gedcomx\\Tests\\Unit\\Conclusion\\DocumentTests::testDocumentDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Conclusion\\DocumentTests::testDocumentGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\DocumentTests::testDocumentWithoutText":0,"Gedcomx\\Tests\\Unit\\Conclusion\\EventTests::testEventDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Conclusion\\EventTests::testEventGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\EventTests::testEventWithoutRoles":0,"Gedcomx\\Tests\\Unit\\Conclusion\\FactTests::testFactDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\FactTests::testFactGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\FactTests::testFactWithoutDate":0,"Gedcomx\\Tests\\Unit\\Conclusion\\GenderTests::testGenderDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\GenderTests::testGenderGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\GenderTests::testGenderWithNullType":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NameTests::testNameDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NameTests::testNameGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NameTests::testNameWithMultipleForms":0,"Gedcomx\\Tests\\Unit\\Conclusion\\PersonTests::testPerson":0.002,"Gedcomx\\Tests\\Unit\\Conclusion\\PlaceDescriptionTests::testPlaceDescriptionDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\PlaceDescriptionTests::testPlaceDescriptionGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\PlaceDescriptionTests::testPlaceDescriptionWithoutCoordinates":0,"Gedcomx\\Tests\\Unit\\Conclusion\\RelationshipTests::testRelationshipDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Conclusion\\RelationshipTests::testRelationshipGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\RelationshipTests::testRelationshipWithoutFacts":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\CommentTests::testCommentDeserialization":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\CommentTests::testCommentGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\CommentTests::testCommentWithoutContributor":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\DiscussionReferenceTests::testDiscussionReferenceGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\DiscussionReferenceTests::testDiscussionReferenceWithoutResourceId":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\DiscussionReferenceTests::testDiscussionReferenceEmpty":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\DiscussionTests::testDiscussionDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\DiscussionTests::testDiscussionGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\DiscussionTests::testDiscussionWithoutComments":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\UserTests::testUserDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\UserTests::testUserGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\UserTests::testUserWithMinimalData":0,"Gedcomx\\Unit\\GedcomxFileTests::testReadGedcomxFile":0.002,"Gedcomx\\Unit\\GedcomxFileTests::testXMLSerialization":0.002,"Gedcomx\\Unit\\GedcomxFileTests::testXMLDeserialization":0.001,"Gedcomx\\Unit\\GedcomxFileTests::testCreateGedxFile":0.003,"Gedcomx\\Tests\\Unit\\Source\\SourceCitationTests::testSourceCitationGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Source\\SourceCitationTests::testSourceCitationWithLanguage":0,"Gedcomx\\Tests\\Unit\\Source\\SourceCitationTests::testSourceCitationEmpty":0,"Gedcomx\\Tests\\Unit\\Source\\SourceDescriptionTests::testSourceDescriptionDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Source\\SourceDescriptionTests::testSourceDescriptionGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Source\\SourceDescriptionTests::testSourceDescriptionWithoutCitations":0,"Gedcomx\\Tests\\Unit\\Source\\SourceReferenceTests::testSourceReferenceGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Source\\SourceReferenceTests::testSourceReferenceWithoutDescription":0,"Gedcomx\\Tests\\Unit\\Source\\SourceReferenceTests::testSourceReferenceWithAttribution":0,"Gedcomx\\Tests\\Unit\\XMLTests::testDeserializeXML":0,"Gedcomx\\Tests\\Unit\\Agent\\OnlineAccountTests::testOnlineAccountDeserialization":0,"Gedcomx\\Tests\\Unit\\Agent\\OnlineAccountTests::testOnlineAccountGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Agent\\OnlineAccountTests::testOnlineAccountWithoutServiceHomepage":0,"Gedcomx\\Tests\\Unit\\Conclusion\\DateInfoTests::testDateInfoDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Conclusion\\DateInfoTests::testDateInfoGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\DateInfoTests::testDateInfoWithoutFormal":0,"Gedcomx\\Tests\\Unit\\Conclusion\\EventRoleTests::testEventRoleDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\EventRoleTests::testEventRoleGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\EventRoleTests::testEventRoleWithoutDetails":0,"Gedcomx\\Tests\\Unit\\Conclusion\\IdentifierTests::testIdentifierDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\IdentifierTests::testIdentifierGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\IdentifierTests::testIdentifierWithoutType":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NameFormTests::testNameFormDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Conclusion\\NameFormTests::testNameFormGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NameFormTests::testNameFormWithoutParts":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NamePartTests::testNamePartDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NamePartTests::testNamePartGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\NamePartTests::testNamePartWithoutType":0,"Gedcomx\\Tests\\Unit\\Conclusion\\PlaceReferenceTests::testPlaceReferenceDeserialization":0,"Gedcomx\\Tests\\Unit\\Conclusion\\PlaceReferenceTests::testPlaceReferenceGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Conclusion\\PlaceReferenceTests::testPlaceReferenceWithoutNormalized":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\ArtifactMetadataTests::testArtifactMetadataConstruction":0.001,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\ArtifactMetadataTests::testArtifactMetadataGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\ArtifactMetadataTests::testArtifactMetadataWithEmptyData":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\ChangeInfoTests::testChangeInfoDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\ChangeInfoTests::testChangeInfoGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\ChangeInfoTests::testChangeInfoWithoutReason":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MatchInfoTests::testMatchInfoDeserialization":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MatchInfoTests::testMatchInfoGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MatchInfoTests::testMatchInfoWithoutCollection":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeAnalysisTests::testMergeAnalysisConstruction":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeAnalysisTests::testMergeAnalysisGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeAnalysisTests::testMergeAnalysisWithEmptyData":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeConflictTests::testMergeConflictConstruction":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeConflictTests::testMergeConflictGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeConflictTests::testMergeConflictWithEmptyData":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeTests::testMergeDeserialization":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeTests::testMergeGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Extensions\\FamilySearch\\MergeTests::testMergeWithoutResources":0,"Gedcomx\\Tests\\Unit\\Source\\CitationFieldTests::testCitationFieldDeserialization":0,"Gedcomx\\Tests\\Unit\\Source\\CitationFieldTests::testCitationFieldGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Source\\CitationFieldTests::testCitationFieldEmpty":0,"Gedcomx\\Tests\\Unit\\Source\\CoverageTests::testCoverageDeserialization":0.001,"Gedcomx\\Tests\\Unit\\Source\\CoverageTests::testCoverageGettersAndSetters":0,"Gedcomx\\Tests\\Unit\\Source\\CoverageTests::testCoverageWithoutSpatial":0}}
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# GedcomX - PHP SDK

[![Packagist](https://img.shields.io/packagist/v/gedcomx/gedcomx-php.svg)](https://packagist.org/packages/gedcomx/gedcomx-php)
[![CI](https://github.com/FamilySearch/gedcomx-php/actions/workflows/ci.yml/badge.svg)](https://github.com/FamilySearch/gedcomx-php/actions/workflows/ci.yml)
[![Build Status](https://travis-ci.org/FamilySearch/gedcomx-php.svg?branch=master)](https://travis-ci.org/FamilySearch/gedcomx-php)
[![Coverage Status](https://coveralls.io/repos/FamilySearch/gedcomx-php/badge.svg?branch=master&service=github)](https://coveralls.io/github/FamilySearch/gedcomx-php?branch=master)
[![Dependency Status](https://www.versioneye.com/user/projects/5633c23236d0ab0016001f02/badge.svg?style=flat)](https://www.versioneye.com/user/projects/5633c23236d0ab0016001f02)

The PHP implementation of [GEDCOM X](http://www.gedcomx.org), including GEDCOM X extension projects.
The library only provides classes for serialization and deserialization of GEDCOM X
Expand Down Expand Up @@ -68,13 +69,6 @@ Do **one** of the following steps to activate Composer and install the gedcomx-p

## Changelog

* v3.1.0
* Migrate from Travis CI to GitHub Actions
* Add multi-version PHP testing (7.4, 8.0, 8.1, 8.2, 8.3)
* Update CI/CD pipeline with automated testing and coverage reporting
* Update README badges to reflect GitHub Actions status
* Added CHANGELOG.md for a more extensive overview on changes

* v3.0.0
* Split out the API networking code into [gedcomx-php-client](https://github.com/FamilySearch/gedcomx-php-client)

Expand Down
282 changes: 282 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
# Testing Guide

## Overview

The GEDCOM X PHP SDK includes a comprehensive test suite using PHPUnit 9.5+ to ensure compatibility across PHP versions 7.4 through 8.3.

## Running Tests

### Prerequisites

- PHP 7.4, 8.0, 8.1, 8.2, or 8.3
- Composer dependencies installed
- Xdebug or PCOV extension (optional, for coverage reports)

### Basic Test Execution

Run the complete test suite:

```bash
vendor/bin/phpunit
```

### Running Specific Tests

Run a specific test file:

```bash
vendor/bin/phpunit tests/unit/PersonTests.php
```

Run a specific test method:

```bash
vendor/bin/phpunit --filter testPerson
```

## Code Coverage

### Generating Coverage Reports

**HTML Report** (most detailed):

```bash
vendor/bin/phpunit --coverage-html build/coverage
```

Then open `build/coverage/index.html` in your browser.

**Text Report** (terminal output):

```bash
vendor/bin/phpunit --coverage-text
```

**Clover XML** (for CI/CD integration):

```bash
vendor/bin/phpunit --coverage-clover build/logs/clover.xml
```

### Coverage Baseline (2026)

Current test coverage baseline established:

- **Test Files**: 28 test suites
- **Test Cases**: 99 tests with 237 assertions
- **Source Files**: 125+ PHP files
- **Core Models**: Complete GEDCOM X conceptual model class coverage
- **Extensions**: Comprehensive FamilySearch platform extension coverage

### Coverage Areas

#### Well-Covered Components (as of v3.2.0)

- **GEDCOM X file format**: .gedx reading and writing, XML serialization/deserialization
- **Core conclusion models**: Person, Gender, Fact, Name, NameForm, NamePart, Event, EventRole, Relationship, Document, PlaceDescription, PlaceReference, DateInfo, Identifier
- **Agent models**: Agent, Address, OnlineAccount
- **Source models**: SourceDescription, SourceReference, SourceCitation, Coverage, CitationField
- **FamilySearch extensions**: User, Discussion, Comment, DiscussionReference, ChildAndParentsRelationship, ChangeInfo, MatchInfo, Merge, MergeAnalysis, MergeConflict, ArtifactMetadata

#### Areas for Future Expansion

Additional test coverage could be added for:

1. **Abstract/Base Classes**
- Subject and Conclusion base classes
- HasFacts trait
- DisplayProperties models

2. **Serialization Edge Cases**
- JSON serialization for all models (currently only XML is comprehensively tested)
- Malformed data handling
- Namespace handling for extensions
- Large file performance

4. **Collections and Utilities**
- Collection class operations
- Reference resolvers
- Model visitor patterns

## Test Structure

### Directory Layout

```
tests/
├── bootstrap.php # Test initialization
├── ApiTestCase.php # Base test case class
├── ArtifactBuilder.php # Test artifact generation
├── PersonBuilder.php # Test person creation
├── TestBuilder.php # General test utilities
├── XMLBuilder.php # XML test utilities
├── files/ # Test fixtures
│ ├── person.json
│ ├── record.xml
│ ├── sample.gedx
│ └── cap-relationship-control.xml
├── tmp/ # Temporary test output
└── unit/ # Unit tests
├── PersonTests.php
├── XMLTests.php
└── GedcomxFileTests.php
```

### Test Fixtures

Test fixtures are located in `tests/files/`:

- **Core model fixtures**: person.json, gender.json, fact.json, name.json, date-info.json, identifier.json, name-form.json, name-part.json, event.json, event-role.json, relationship.json, document.json, place-description.json, place-reference.json
- **Agent fixtures**: agent.json, address.json, online-account.json
- **Source fixtures**: source-description.json, source-citation.json, coverage.json, citation-field.json
- **FamilySearch extension fixtures**: user.json, discussion.json, comment.json, change-info.json, match-info.json, merge.json
- **XML fixtures**: record.xml, cap-relationship-control.xml
- **Archive files**: sample.gedx
- **Test images**: test-image.jpg (for GEDX file tests)

### Base Test Class

All tests extend `Gedcomx\Tests\ApiTestCase`, which provides:

- Automatic setup and teardown
- Temporary directory management
- Test fixture loading helpers
- Common test utilities

## PHP Version Compatibility

### Tested Versions

The SDK is automatically tested on:

- PHP 7.4
- PHP 8.0
- PHP 8.1
- PHP 8.2
- PHP 8.3

### Compatibility Notes

**PHP 8.0+ Changes Addressed**:

- ✅ Explicit nullable type declarations (`?Type` instead of implicit null defaults)
- ✅ Return type declarations for interface implementations (ArrayAccess, Countable, IteratorAggregate)
- ✅ Modern PHPUnit assertions (removed deprecated `assertEqualXMLStructure`)

**External Dependencies**:

Test fixtures are used instead of runtime image generation, eliminating external dependencies and ensuring consistent test behavior across all PHP versions.

## Continuous Integration

### GitHub Actions Workflow

The CI pipeline (`.github/workflows/ci.yml`) automatically:

1. Tests against all supported PHP versions
2. Validates composer.json and composer.lock
3. Runs the complete test suite
4. Generates code coverage reports
5. Uploads coverage to Coveralls (PHP 8.3 only)

### Running CI Locally

Simulate CI testing for a specific PHP version using Docker:

```bash
docker run --rm -v $(pwd):/app -w /app php:8.3-cli bash -c \
"apt-get update && apt-get install -y git zip unzip && \
php -r \"copy('https://getcomposer.org/installer', 'composer-setup.php');\" && \
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
composer install --prefer-dist --no-progress && \
vendor/bin/phpunit"
```

## Writing New Tests

### Best Practices

1. **Extend ApiTestCase**: Use the base test case for common functionality
2. **Use Fixtures**: Place test data files in `tests/files/`
3. **Clean Up**: Tests automatically clean up `tests/tmp/` after each run
4. **Assertions**: Use modern PHPUnit assertions
5. **Naming**: Test files should end with `Tests.php` (e.g., `PersonTests.php`)
6. **Namespace**: Use `Gedcomx\Tests\Unit` or `Gedcomx\Tests\Integration`

### Example Test

```php
<?php

namespace Gedcomx\Tests\Unit;

use Gedcomx\Tests\ApiTestCase;
use Gedcomx\Conclusion\Person;

class PersonTests extends ApiTestCase
{
public function testPersonDeserialization()
{
$person = new Person($this->loadJson('person.json'));

$this->assertEquals('PPPJ-MYZ', $person->getId());
$this->assertCount(2, $person->getFacts());
$this->assertCount(1, $person->getNames());
}
}
```

## Troubleshooting

### Coverage Not Generated

If coverage reports aren't generated:

1. Install Xdebug:
```bash
pecl install xdebug
```

2. Or install PCOV (faster):
```bash
pecl install pcov
```

3. Verify installation:
```bash
php -m | grep -i xdebug
# or
php -m | grep -i pcov
```

### Tests Failing on PHP 8+

If you see deprecation warnings or failures on PHP 8+:

1. Ensure you're using PHPUnit 9.5+ (check `composer.json`)
2. Verify explicit nullable types are used (`?Type` not `Type = null`)
3. Check that interface implementations have proper return types

### Permission Issues with tests/tmp/

Ensure the temporary directory is writable:

```bash
chmod 755 tests/tmp
```

## Contributing

When contributing new features:

1. Add unit tests for new functionality
2. Ensure all tests pass on all supported PHP versions
3. Maintain or improve code coverage
4. Follow existing test patterns and structure
5. Update this document if adding new test categories

## References

- [PHPUnit Documentation](https://phpunit.de/documentation.html)
- [GEDCOM X Specification](http://www.gedcomx.org)
- [FamilySearch API Documentation](https://www.familysearch.org/developers/)
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"require-dev": {
"phpunit/phpunit": "^9.5",
"fakerphp/faker": "^1.9",
"intervention/image": "^2.7",
"php-coveralls/php-coveralls": "^2.5"
},
"autoload": {
Expand Down
18 changes: 13 additions & 5 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="./tests/bootstrap.php"
colors="true"
processIsolation="false"
stopOnFailure="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true">
beStrictAboutOutputDuringTests="true"
failOnRisky="false"
failOnWarning="false">

<testsuites>
<testsuite name="Gedcomx">
<directory suffix="Tests.php">tests/unit</directory>
</testsuite>
</testsuites>

<coverage>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
<exclude>
<directory>./src/Rs/Client</directory>
</exclude>
<report>
<text outputFile="php://stdout" showUncoveredFiles="false"/>
</report>
</coverage>

</phpunit>
2 changes: 1 addition & 1 deletion src/GedcomxFile/GedcomxFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class GedcomxFile
*
* @throws \Gedcomx\GedcomxFile\GedcomxFileException
*/
public function __construct($filepath, GedcomxEntryDeserializer $deserializer = null)
public function __construct($filepath, ?GedcomxEntryDeserializer $deserializer = null)
{
$this->deserializer = $deserializer;
if( $this->deserializer == null){
Expand Down
Loading