Skip to content

Commit

Permalink
Adding display parameter to ValueCoding (#68)
Browse files Browse the repository at this point in the history
# Adding display parameter to ValueCoding

## Currently `ValueCoding` doesn't contain the display property defined
by [FHIR standard](https://www.hl7.org/fhir/datatypes.html#Coding)
This is a limitation in case the response of the questionnaire is
presented on other platforms like FE or Android which use this property
for displaying the text of the answer.


## ⚙️ Release Notes 
* property `display` was added to the `ValueCoding` structure.


## 📚 Documentation
* The FHIR standard defines a display attribute under the Coding 
* This property was unfortunately missing in the current implementation
of `ValueCoding`
* The cardinality of the attribute is according to
[FHIR](https://www.hl7.org/fhir/datatypes.html#Coding) 0..1
* Because of this, the property is defined as an `Optional<String>` in
Swift.
* In case this property is missing, decoding will not fail thanks to
`decodeIfPresent`


## ✅ Testing
* Unit tests have been updated and are successfully passing.


### Code of Conduct & Contributing Guidelines 
By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md).

---------

Co-authored-by: Paul Schmiedmayer <[email protected]>
Co-authored-by: Vishnu Ravi <[email protected]>
  • Loading branch information
3 people authored Mar 1, 2024
1 parent ef35067 commit 7c2efdc
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 29 deletions.
63 changes: 50 additions & 13 deletions Example/ExampleUITests/ExampleUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -537,16 +537,16 @@ final class ExampleUITests: XCTestCase {
// Dismiss results view
app.swipeDown(velocity: XCUIGestureVelocity.fast)
}

func testMultipleEnableWhenExample() throws {
func testMultipleEnableWhenExampleWithAllAnswersCorrect() throws {
let app = XCUIApplication()
app.launch()

let multipleEnableWhenButton = app.collectionViews.buttons["Multiple EnableWhen Expressions"]
XCTAssert(multipleEnableWhenButton.waitForExistence(timeout: 2))
multipleEnableWhenButton.tap()

// We will first answer all questions correctly
// Answer all questions correctly
XCTAssert(app.tables.staticTexts["Yes"].waitForExistence(timeout: 2))
app.tables.staticTexts["Yes"].tap()
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
Expand All @@ -557,34 +557,38 @@ final class ExampleUITests: XCTestCase {
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()


let integerField = app.textFields.element(boundBy: 0)
XCTAssert(integerField.waitForExistence(timeout: 2))
integerField.tap()
integerField.typeText("12\n")
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

// First result screen appears if at least one answer is correct.
sleep(1)
// First result screen should appear since at least one answer is correct.
sleep(3)
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

// Second result screen appears if all answers are correct.
sleep(1)
// Second result screen should appear since all answers are correct.
sleep(3)
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

// Now the completion screen will appear with a "Done" button that we can tap
sleep(1)
sleep(3)
XCTAssert(app.buttons["Done"].waitForExistence(timeout: 2))
app.buttons["Done"].tap()

// Now we relaunch the survey
}

func testMultipleEnableWhenExampleWithOneAnswerCorrect() throws {
let app = XCUIApplication()
app.launch()

let multipleEnableWhenButton = app.collectionViews.buttons["Multiple EnableWhen Expressions"]
XCTAssert(multipleEnableWhenButton.waitForExistence(timeout: 2))
multipleEnableWhenButton.tap()

// This time we answer only one question correctly
// Answer only one question correctly
XCTAssert(app.tables.staticTexts["Yes"].waitForExistence(timeout: 2))
app.tables.staticTexts["Yes"].tap()
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
Expand All @@ -595,14 +599,47 @@ final class ExampleUITests: XCTestCase {
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

let integerField = app.textFields.element(boundBy: 0)
XCTAssert(integerField.waitForExistence(timeout: 2))
integerField.tap()
integerField.typeText("2\n")
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

// Only one result screen should appear.
sleep(1)
sleep(3)
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

// Now the completion screen should appear.
sleep(3)
XCTAssert(app.buttons["Done"].waitForExistence(timeout: 2))
app.buttons["Done"].tap()
}

func testMultipleEnableWhenExampleWithNoAnswerCorrect() throws {
let app = XCUIApplication()
app.launch()

let multipleEnableWhenButton = app.collectionViews.buttons["Multiple EnableWhen Expressions"]
XCTAssert(multipleEnableWhenButton.waitForExistence(timeout: 2))
multipleEnableWhenButton.tap()

// Answer all questions incorrectly
XCTAssert(app.tables.staticTexts["Yes"].waitForExistence(timeout: 2))
app.tables.staticTexts["No"].tap()
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

XCTAssert(app.tables.staticTexts["orange"].waitForExistence(timeout: 2))
app.tables.staticTexts["orange"].tap()
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

let integerField = app.textFields.element(boundBy: 0)
XCTAssert(integerField.waitForExistence(timeout: 2))
integerField.tap()
integerField.typeText("2\n")
XCTAssert(app.buttons["Next"].waitForExistence(timeout: 2))
app.buttons["Next"].tap()

Expand Down
7 changes: 5 additions & 2 deletions Sources/FHIRQuestionnaires/Resources/MultipleEnableWhen.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@
"question": "c3fd1ee9-1f0d-4136-887c-f22589bbdc68",
"operator": "=",
"answerCoding": {
"code": "green",
"system": "urn:uuid:2dab6b0b-4518-4c39-9f7f-a9da98e7c2f3",
"code": "green"
"display": "green"
}
},
{
Expand All @@ -137,8 +138,9 @@
"question": "c3fd1ee9-1f0d-4136-887c-f22589bbdc68",
"operator": "=",
"answerCoding": {
"code": "green",
"system": "urn:uuid:2dab6b0b-4518-4c39-9f7f-a9da98e7c2f3",
"code": "green"
"display": "green"
}
},
{
Expand All @@ -147,6 +149,7 @@
"answerInteger": 12
}
],
"enableBehavior": "all"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ extension QuestionnaireItemEnableWhen {
let resultSelector = ORKResultSelector(resultIdentifier: enableQuestionId)
let predicate: NSPredicate?

// The translation from FHIR to ResearchKit preedicates requires negating the FHIR preedicates as FHIR preedicates activate steps while ResearchKit uses them to skip steps
// The translation from FHIR to ResearchKit predicates requires negating the FHIR predicates as FHIR predicates activate steps while ResearchKit uses them to skip steps
switch answer {
case .coding(let coding):
predicate = try coding.predicate(with: resultSelector, operator: fhirOperator)
Expand Down Expand Up @@ -90,7 +90,7 @@ extension Coding {
return nil
}

let expectedAnswer = ValueCoding(code: code, system: system)
let expectedAnswer = ValueCoding(code: code, system: system, display: display?.value?.string)

let predicate = ORKResultPredicate.predicateForChoiceQuestionResult(
with: resultSelector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ extension QuestionnaireItem {
let system = valueSet?.compose?.include.first?.system?.value?.url.absoluteString else {
continue
}
let valueCoding = ValueCoding(code: code, system: system)
let valueCoding = ValueCoding(code: code, system: system, display: display)
let choice = ORKTextChoice(text: display, value: valueCoding.rawValue as NSSecureCoding & NSCopying & NSObjectProtocol)
choices.append(choice)
}
Expand All @@ -259,15 +259,16 @@ extension QuestionnaireItem {
let system = coding.system?.value?.url.absoluteString else {
continue
}
let valueCoding = ValueCoding(code: code, system: system)
let valueCoding = ValueCoding(code: code, system: system, display: display)
let choice = ORKTextChoice(text: display, value: valueCoding.rawValue as NSSecureCoding & NSCopying & NSObjectProtocol)
print(valueCoding.rawValue as NSSecureCoding & NSCopying & NSObjectProtocol)
choices.append(choice)
}

if openChoice {
// If the `QuestionnaireItemType` is `open-choice`, allow user to enter in their own free-text answer.
let otherChoiceText = NSLocalizedString("Other", comment: "")
let otherChoiceResult = ValueCoding(code: "other", system: "other")
let otherChoiceResult = ValueCoding(code: "other", system: "other", display: otherChoiceText)
let otherChoice = ORKTextChoiceOther.choice(
withText: otherChoiceText,
detailText: nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ extension ORKTaskResult {
if let valueCodingString = answer as? String, let valueCoding = ValueCoding(rawValue: valueCodingString) {
let coding = Coding(
code: FHIRPrimitive(FHIRString(valueCoding.code)),
display: valueCoding.display.map { FHIRPrimitive(FHIRString($0)) },
system: FHIRPrimitive(FHIRURI(stringLiteral: valueCoding.system))
)
responses += [.coding(coding)]
Expand Down
14 changes: 10 additions & 4 deletions Sources/ResearchKitOnFHIR/ValueCoding/ValueCoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,30 @@ struct ValueCoding: Equatable, Codable, RawRepresentable {
enum CodingKeys: String, CodingKey {
case code
case system
case display
}


let code: String
let system: String

let display: String?

var rawValue: String {
guard let data = try? JSONEncoder().encode(self) else {
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]

guard let data = try? encoder.encode(self) else {
return "{}"
}

return String(decoding: data, as: UTF8.self)
}


init(code: String, system: String) {
init(code: String, system: String, display: String?) {
self.code = code
self.system = system
self.display = display
}

init?(rawValue: String) {
Expand All @@ -47,12 +52,13 @@ struct ValueCoding: Equatable, Codable, RawRepresentable {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.code = try values.decode(String.self, forKey: .code)
self.system = try values.decode(String.self, forKey: .system)
self.display = try values.decodeIfPresent(String.self, forKey: .display)
}


func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(code, forKey: .code)
try container.encode(system, forKey: .system)
try container.encode(display, forKey: .display)
}
}
12 changes: 7 additions & 5 deletions Tests/ResearchKitOnFHIRTests/ResearchKitToFHIRTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ final class ResearchKitToFHIRTests: XCTestCase {
}

func testSingleChoiceResponse() {
let testValue = ValueCoding(code: "testCode", system: "http://biodesign.stanford.edu/test-system")
let testValue = ValueCoding(code: "testCode", system: "http://biodesign.stanford.edu/test-system", display: "Test Code")

let choiceResult = ORKChoiceQuestionResult(identifier: "choiceResult")
choiceResult.choiceAnswers = [testValue.rawValue as NSSecureCoding & NSCopying & NSObjectProtocol]
Expand All @@ -198,12 +198,13 @@ final class ResearchKitToFHIRTests: XCTestCase {
switch answer {
case let .coding(coding):
guard let code = coding.code?.value?.string,
let display = coding.display?.value?.string,
let system = coding.system?.value?.url.absoluteString else {
XCTFail("Could not extract the code and system from the coding.")
return
}

let valueCoding = ValueCoding(code: code, system: system)
let valueCoding = ValueCoding(code: code, system: system, display: display)
XCTAssertEqual(testValue, valueCoding)

default:
Expand All @@ -214,8 +215,8 @@ final class ResearchKitToFHIRTests: XCTestCase {

func testMultipleChoiceResponse() {
let testValues = [
ValueCoding(code: "testCode1", system: "http://biodesign.stanford.edu/test-system"),
ValueCoding(code: "testCode2", system: "http://biodesign.stanford.edu/test-system")
ValueCoding(code: "testCode1", system: "http://biodesign.stanford.edu/test-system", display: "Test Code 1"),
ValueCoding(code: "testCode2", system: "http://biodesign.stanford.edu/test-system", display: "Test Code 2")
]

let choiceResult = ORKChoiceQuestionResult(identifier: "choiceResult")
Expand All @@ -240,12 +241,13 @@ final class ResearchKitToFHIRTests: XCTestCase {
switch answer {
case let .coding(coding):
guard let code = coding.code?.value?.string,
let display = coding.display?.value?.string,
let system = coding.system?.value?.url.absoluteString else {
XCTFail("Could not extract the code and system from the coding.")
return
}

let valueCoding = ValueCoding(code: code, system: system)
let valueCoding = ValueCoding(code: code, system: system, display: display)
XCTAssertEqual(testValues[index], valueCoding)

default:
Expand Down

0 comments on commit 7c2efdc

Please sign in to comment.