import { test } from '@japa/runner'
import { DataPropertyKind, Thing, type PropertyBinding, type PropertyWebBindings } from '../../../../src/index.js'
import {
  DataEntity,
  DataModel,
  DataNamespace,
  DataProperty,
  type IDataProperty,
  type DataPropertySchema,
} from '../../../../src/legacy.js'

test.group('constructor() with defaults', (group) => {
  let root: DataNamespace
  group.each.setup(() => {
    root = new DataNamespace()
  })

  test('sets the kind', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.equal(assoc.kind, DataPropertyKind)
  })

  test('sets the key', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.typeOf(assoc.key, 'string')
    assert.isNotEmpty(assoc.key)
  })

  test('sets the default "info"', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.typeOf(assoc.info, 'EventTarget')
    assert.equal(assoc.info.name, '')
  })

  test('sets the default "type"', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.equal(assoc.type, 'string')
  })

  test('sets the default "tags"', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.deepEqual(assoc.tags, [])
  })

  test('sets the default "taxonomy"', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.deepEqual(assoc.taxonomy, [])
  })

  test('sets the default "schema"', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.schema)
  })

  test('does not set multiple', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.multiple)
  })

  test('does not set required', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.required)
  })

  test('does not set primary', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.primary)
  })

  test('does not set index', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.index)
  })

  test('does not set readOnly', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.readOnly)
  })

  test('does not set writeOnly', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.writeOnly)
  })

  test('does not set deprecated', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.isUndefined(assoc.deprecated)
  })
})

test.group('constructor() from schema initialization', (group) => {
  let root: DataNamespace
  group.each.setup(() => {
    root = new DataNamespace()
  })

  test('sets the key', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.key = 'test'
    const instance = new DataProperty(root, orig)
    assert.equal(instance.key, 'test')
  })

  test('sets the info', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.info.name = 'test'
    const instance = new DataProperty(root, orig)
    assert.equal(instance.info.name, 'test')
  })

  test('sets the multiple', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.multiple = true
    const instance = new DataProperty(root, orig)
    assert.isTrue(instance.multiple)
  })

  test('sets the required', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.required = true
    const instance = new DataProperty(root, orig)
    assert.isTrue(instance.required)
  })

  test('sets the "type"', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.type = 'date'
    const assoc = new DataProperty(root, orig)
    assert.equal(assoc.type, 'date')
  })

  test('sets the "tags"', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.tags = ['test']
    const assoc = new DataProperty(root, orig)
    assert.deepEqual(assoc.tags, ['test'])
  })

  test('sets the "taxonomy"', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.taxonomy = ['test']
    const assoc = new DataProperty(root, orig)
    assert.deepEqual(assoc.taxonomy, ['test'])
  })

  test('sets the "schema"', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.schema = {
      enum: ['1'],
    }
    const assoc = new DataProperty(root, orig)
    assert.deepEqual(assoc.schema, orig.schema)
  })

  test('sets the primary', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.primary = true
    const assoc = new DataProperty(root, orig)
    assert.isTrue(assoc.primary)
  })

  test('sets the index', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.index = true
    const assoc = new DataProperty(root, orig)
    assert.isTrue(assoc.index)
  })

  test('sets the readOnly', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.readOnly = true
    const assoc = new DataProperty(root, orig)
    assert.isTrue(assoc.readOnly)
  })

  test('sets the writeOnly', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.writeOnly = true
    const assoc = new DataProperty(root, orig)
    assert.isTrue(assoc.writeOnly)
  })

  test('sets the deprecated', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.deprecated = true
    const assoc = new DataProperty(root, orig)
    assert.isTrue(assoc.deprecated)
  })

  test('initializes from JSON schema ', ({ assert }) => {
    const orig = new DataProperty(root).toJSON()
    orig.required = true
    const instance = new DataProperty(root, JSON.stringify(orig))
    assert.isTrue(instance.required)
  })
})

test.group('fromName()', (group) => {
  let root: DataNamespace
  group.each.setup(() => {
    root = new DataNamespace()
  })

  test('sets the name', ({ assert }) => {
    const assoc = DataProperty.fromName(root, 'test')
    assert.equal(assoc.info.name, 'test')
  })
})

test.group('fromType()', (group) => {
  let root: DataNamespace
  group.each.setup(() => {
    root = new DataNamespace()
  })

  test('sets the type', ({ assert }) => {
    const assoc = DataProperty.fromType(root, 'number')
    assert.equal(assoc.type, 'number')
  })
})

test.group('new()', (group) => {
  let root: DataNamespace
  let base: IDataProperty
  group.each.setup(() => {
    root = new DataNamespace()
    base = {
      info: Thing.fromName('test name').toJSON(),
      key: 'test123',
      kind: DataPropertyKind,
      type: 'binary',
    }
  })

  test('sets the key', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.equal(assoc.key, 'test123')
  })

  test('sets the info', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.equal(assoc.info.name, 'test name')
  })

  test('sets the type', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.equal(assoc.type, 'binary')
  })

  test('sets default info', ({ assert }) => {
    const assoc = new DataProperty(root)
    // @ts-expect-error Used in testing
    delete base.info
    assoc.new(base)
    assert.equal(assoc.info.name, '')
  })

  test('sets the multiple', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new({ ...base, multiple: true })
    assert.isTrue(assoc.multiple)
  })

  test('does not set multiple when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.isUndefined(assoc.multiple)
  })

  test('sets the required', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new({ ...base, required: true })
    assert.isTrue(assoc.required)
  })

  test('does not set required when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.isUndefined(assoc.required)
  })

  test('sets the index', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new({ ...base, index: true })
    assert.isTrue(assoc.index)
  })

  test('does not set index when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.isUndefined(assoc.index)
  })

  test('sets the readOnly', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new({ ...base, readOnly: true })
    assert.isTrue(assoc.readOnly)
  })

  test('does not set readOnly when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.isUndefined(assoc.readOnly)
  })

  test('sets the writeOnly', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new({ ...base, writeOnly: true })
    assert.isTrue(assoc.writeOnly)
  })

  test('does not set writeOnly when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.isUndefined(assoc.writeOnly)
  })

  test('sets the deprecated', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new({ ...base, deprecated: true })
    assert.isTrue(assoc.deprecated)
  })

  test('does not set deprecated when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.isUndefined(assoc.deprecated)
  })

  test('sets the primary', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new({ ...base, primary: true })
    assert.isTrue(assoc.primary)
  })

  test('does not set primary when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.new(base)
    assert.isUndefined(assoc.primary)
  })

  test('sets the tags as a copy', ({ assert }) => {
    const assoc = new DataProperty(root)
    const init = { ...base, tags: ['a'] }
    assoc.new(init)
    assert.deepEqual(assoc.tags, ['a'])
    init.tags.push('b')
    assert.deepEqual(assoc.tags, ['a'])
  })

  test('resets tags when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.tags = ['a']
    assoc.new(base)
    assert.deepEqual(assoc.tags, [])
  })

  test('sets the taxonomy as a copy', ({ assert }) => {
    const assoc = new DataProperty(root)
    const init = { ...base, taxonomy: ['a'] }
    assoc.new(init)
    assert.deepEqual(assoc.taxonomy, ['a'])
    init.taxonomy.push('b')
    assert.deepEqual(assoc.taxonomy, ['a'])
  })

  test('resets taxonomy when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assoc.taxonomy = ['a']
    assoc.new(base)
    assert.deepEqual(assoc.taxonomy, [])
  })

  test('sets the schemas as a copy', ({ assert }) => {
    const assoc = new DataProperty(root)
    const schema: DataPropertySchema = {
      enum: ['1'],
    }
    const init = { ...base, schema }
    assoc.new(init)
    assert.deepEqual(assoc.schema, schema)
    schema.enum!.push('other')
    assert.deepEqual(assoc.schema!.enum, ['1'])
  })

  test('resets schemas when not in the input', ({ assert }) => {
    const assoc = new DataProperty(root)
    const schema: DataPropertySchema = {
      enum: ['1'],
    }
    assoc.schema = schema
    assoc.new(base)
    assert.isUndefined(assoc.schema)
  })

  test('throws when unknown input', ({ assert }) => {
    const assoc = new DataProperty(root)
    assert.throws(() => {
      // @ts-expect-error Used in testing
      assoc.new({})
    }, 'Not a data property.')
  })
})

test.group('toJSON()', (group) => {
  let root: DataNamespace
  let base: DataProperty
  group.each.setup(() => {
    root = new DataNamespace()
    base = new DataProperty(root, {
      info: Thing.fromName('test name').toJSON(),
      key: 'test123',
      kind: DataPropertyKind,
      type: 'number',
    })
  })

  test('serializes the kind', ({ assert }) => {
    const result = base.toJSON()
    assert.equal(result.kind, DataPropertyKind)
  })

  test('serializes the key', ({ assert }) => {
    const result = base.toJSON()
    assert.equal(result.key, base.key)
  })

  test('serializes the info', ({ assert }) => {
    const result = base.toJSON()
    assert.equal(result.info.name, 'test name')
  })

  test('does not serialize the multiple by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.multiple)
  })

  test('does not serialize the required by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.required)
  })

  test('does not serialize the primary by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.primary)
  })

  test('does not serialize the index by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.index)
  })

  test('does not serialize the readOnly by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.readOnly)
  })

  test('does not serialize the writeOnly by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.writeOnly)
  })

  test('does not serialize the schema by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.schema)
  })

  test('serialize the set multiple', ({ assert }) => {
    base.multiple = false
    const result = base.toJSON()
    assert.isFalse(result.multiple)
  })

  test('serialize the set required', ({ assert }) => {
    base.required = false
    const result = base.toJSON()
    assert.isFalse(result.required)
  })

  test('serialize the set primary', ({ assert }) => {
    base.primary = false
    const result = base.toJSON()
    assert.isFalse(result.primary)
  })

  test('serialize the set index', ({ assert }) => {
    base.index = false
    const result = base.toJSON()
    assert.isFalse(result.index)
  })

  test('serialize the set readOnly', ({ assert }) => {
    base.readOnly = false
    const result = base.toJSON()
    assert.isFalse(result.readOnly)
  })

  test('serialize the set writeOnly', ({ assert }) => {
    base.writeOnly = false
    const result = base.toJSON()
    assert.isFalse(result.writeOnly)
  })

  test('does not serialize taxonomy by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.taxonomy)
  })

  test('does not serialize tags by default', ({ assert }) => {
    const result = base.toJSON()
    assert.isUndefined(result.tags)
  })

  test('serializes tags as a copy', ({ assert }) => {
    base.tags = ['a']
    const result = base.toJSON()
    assert.deepEqual(result.tags, ['a'])
    base.tags.push('b')
    assert.deepEqual(result.tags, ['a'])
  })

  test('serializes taxonomy as a copy', ({ assert }) => {
    base.taxonomy = ['a']
    const result = base.toJSON()
    assert.deepEqual(result.taxonomy, ['a'])
    base.taxonomy.push('b')
    assert.deepEqual(result.taxonomy, ['a'])
  })
})

test.group('remove()', (group) => {
  let root: DataNamespace
  let m1: DataModel
  let e1: DataEntity
  let p1: DataProperty

  group.each.setup(() => {
    root = new DataNamespace()
    m1 = root.addDataModel('m1')
    e1 = m1.addEntity('e1')
    p1 = e1.addNamedProperty('test prop')
  })

  test('removes self from the parent entity', ({ assert }) => {
    p1.remove()
    assert.deepEqual(e1.properties, [])
  })

  test('removes self from the root definitions', ({ assert }) => {
    p1.remove()
    assert.deepEqual(root.definitions.properties, [])
  })

  test('does not serialize properties', ({ assert }) => {
    p1.remove()
    const schema = root.toJSON()
    assert.isUndefined(schema.definitions.properties)
    assert.isUndefined(schema.definitions.entities![0].properties)
  })

  test('removes self only', ({ assert }) => {
    const p2 = e1.addNamedProperty('other')
    p1.remove()
    assert.deepEqual(e1.properties, [p2])
    assert.deepEqual(root.definitions.properties, [p2])
  })
})

test.group('addTag()', (group) => {
  let root: DataNamespace
  let m1: DataModel
  let e1: DataEntity
  let p1: DataProperty

  group.each.setup(() => {
    root = new DataNamespace()
    m1 = root.addDataModel('m1')
    e1 = m1.addEntity('e1')
    p1 = e1.addNamedProperty('test prop')
  })

  test('ignores when empty', ({ assert }) => {
    p1.addTag('')
    assert.deepEqual(p1.tags, [])
  })

  test('adds a tag to the property', ({ assert }) => {
    p1.addTag('Test')
    assert.deepEqual(p1.tags, ['Test'])
  })

  test('ignores a tag case insensitive', ({ assert }) => {
    p1.addTag('Test')
    p1.addTag('teSt')
    assert.deepEqual(p1.tags, ['Test'])
  })

  test('adds a tag the root definitions', ({ assert }) => {
    p1.addTag('Test')
    assert.deepEqual(root.definitions.tags, ['Test'])
  })

  test('ignores adding to root definitions when tag exists case insensitive', ({ assert }) => {
    p1.addTag('Test')
    p1.addTag('TeSt')
    assert.deepEqual(root.definitions.tags, ['Test'])
  })
})

test.group('removeTag()', (group) => {
  let root: DataNamespace
  let m1: DataModel
  let e1: DataEntity
  let p1: DataProperty

  group.each.setup(() => {
    root = new DataNamespace()
    m1 = root.addDataModel('m1')
    e1 = m1.addEntity('e1')
    p1 = e1.addNamedProperty('test prop')
  })

  test('ignores when empty', ({ assert }) => {
    p1.addTag('t1')
    p1.removeTag('')
    assert.deepEqual(p1.tags, ['t1'])
  })

  test('removes the tag from the property', ({ assert }) => {
    p1.addTag('t1')
    p1.removeTag('t1')
    assert.deepEqual(p1.tags, [])
  })

  test('removes only the selected tag', ({ assert }) => {
    p1.addTag('t1')
    p1.addTag('t2')
    p1.addTag('t3')
    p1.removeTag('t2')
    assert.deepEqual(p1.tags, ['t1', 't3'])
  })

  test('does not remove root tags', ({ assert }) => {
    p1.addTag('t1')
    p1.removeTag('t1')
    assert.deepEqual(root.definitions.tags, ['t1'])
  })
})

test.group('toApiShape()', (group) => {
  let root: DataNamespace
  let m1: DataModel
  let e1: DataEntity
  let p1: DataProperty

  group.each.setup(() => {
    root = new DataNamespace()
    m1 = root.addDataModel('m1')
    e1 = m1.addEntity('e1')
    p1 = e1.addNamedProperty('test prop')
  })

  // these tests only check whether the AmfShapeGenerator is called.
  // specific tests are performed elsewhere

  test('returns an object', ({ assert }) => {
    const result = p1.toApiShape()

    assert.typeOf(result, 'object')
    assert.typeOf(result.range, 'object')
  })
})

test.group('isValidType()', () => {
  test('returns true for valid types', ({ assert }) => {
    assert.isTrue(DataProperty.isValidType('string'))
    assert.isTrue(DataProperty.isValidType('number'))
    assert.isTrue(DataProperty.isValidType('boolean'))
    assert.isTrue(DataProperty.isValidType('date'))
    assert.isTrue(DataProperty.isValidType('datetime'))
    assert.isTrue(DataProperty.isValidType('time'))
    assert.isTrue(DataProperty.isValidType('binary'))
  })

  test('returns false for invalid types', ({ assert }) => {
    assert.isFalse(DataProperty.isValidType('invalid'))
    assert.isFalse(DataProperty.isValidType(123))
    assert.isFalse(DataProperty.isValidType(null))
    assert.isFalse(DataProperty.isValidType(undefined))
  })
})

test.group('isValidAttribute()', () => {
  test('returns true for valid attributes', ({ assert }) => {
    assert.isTrue(DataProperty.isValidAttribute('required'))
    assert.isTrue(DataProperty.isValidAttribute('multiple'))
    assert.isTrue(DataProperty.isValidAttribute('primary'))
    assert.isTrue(DataProperty.isValidAttribute('index'))
    assert.isTrue(DataProperty.isValidAttribute('readOnly'))
    assert.isTrue(DataProperty.isValidAttribute('writeOnly'))
    assert.isTrue(DataProperty.isValidAttribute('deprecated'))
  })

  test('returns false for invalid attributes', ({ assert }) => {
    assert.isFalse(DataProperty.isValidAttribute('invalid'))
    assert.isFalse(DataProperty.isValidAttribute(123))
    assert.isFalse(DataProperty.isValidAttribute(null))
    assert.isFalse(DataProperty.isValidAttribute(undefined))
  })
})

test.group('isValidFormat()', () => {
  test('returns true for valid formats', ({ assert }) => {
    assert.isTrue(DataProperty.isValidFormat('float'))
    assert.isTrue(DataProperty.isValidFormat('double'))
    assert.isTrue(DataProperty.isValidFormat('int32'))
    assert.isTrue(DataProperty.isValidFormat('int64'))
    assert.isTrue(DataProperty.isValidFormat('hex'))
    assert.isTrue(DataProperty.isValidFormat('base64'))
    assert.isTrue(DataProperty.isValidFormat(undefined))
  })

  test('returns false for invalid formats', ({ assert }) => {
    assert.isFalse(DataProperty.isValidFormat('invalid'))
    assert.isFalse(DataProperty.isValidFormat(123))
    assert.isFalse(DataProperty.isValidFormat(null))
  })
})

test.group('ensureSchema()', () => {
  test('creates a schema object if it does not exist', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    assert.isUndefined(property.schema)
    const schema = property.ensureSchema()
    assert.isObject(schema)
    assert.deepEqual(property.schema, schema)
  })

  test('returns the existing schema object', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    const existingSchema = { enum: ['a', 'b'] }
    property.schema = existingSchema
    const schema = property.ensureSchema()
    assert.deepEqual(schema, existingSchema)
    assert.deepEqual(property.schema, existingSchema)
  })
})

test.group('Bindings', () => {
  test('add a web binding', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    const webBinding = property.getWebBinding()
    webBinding.format = 'int32'
    assert.equal(property.bindings[0].type, 'web')
    const wb = property.bindings[0].schema as PropertyWebBindings
    assert.equal(wb.format, 'int32')
  })
})

test.group('getWebBinding()', () => {
  test('creates a web binding if it does not exist', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    assert.lengthOf(property.bindings, 0)
    const binding = property.getWebBinding()
    assert.isObject(binding)
    assert.lengthOf(property.bindings, 1)
    assert.equal(property.bindings[0].type, 'web')
    assert.deepEqual(property.bindings[0].schema, binding)
  })

  test('returns the existing web binding', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    const existingBinding: PropertyBinding = { type: 'web', schema: { hidden: true } }
    property.bindings.push(existingBinding)
    const binding = property.getWebBinding()
    assert.deepEqual(binding, existingBinding.schema)
    assert.deepEqual(property.bindings[0].schema, binding)
  })
})

test.group('readBinding()', () => {
  test('returns the existing web binding', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    const existingBinding: PropertyBinding = { type: 'web', schema: { hidden: true } }
    property.bindings.push(existingBinding)
    const binding = property.readBinding('web')
    assert.deepEqual(binding, existingBinding.schema)
  })

  test('returns the existing protobuf binding', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    const existingBinding: PropertyBinding = { type: 'protobuf', schema: { field: 1 } }
    property.bindings.push(existingBinding)
    const binding = property.readBinding('protobuf')
    assert.deepEqual(binding, existingBinding.schema)
  })

  test('returns undefined when binding does not exist', ({ assert }) => {
    const root = new DataNamespace()
    const property = new DataProperty(root)
    const binding = property.readBinding('web')
    assert.isUndefined(binding)
  })
})

test.group('DataProperty.isChildOf()', (group) => {
  let root: DataNamespace
  let n1: DataNamespace
  let n2: DataNamespace
  let m1: DataModel
  let m2: DataModel
  let e1: DataEntity
  let e2: DataEntity
  let p1: DataProperty
  let p2: DataProperty
  let p3: DataProperty

  group.each.setup(() => {
    root = new DataNamespace()
    n1 = root.addNamespace('n1')
    n2 = n1.addNamespace('n2')
    m1 = root.addDataModel('m1')
    m2 = n1.addDataModel('m2')
    e1 = m1.addEntity('e1')
    e2 = m2.addEntity('e2')
    p1 = e1.addNamedProperty('p1')
    p2 = e2.addNamedProperty('p2')
    p3 = n2.addDataModel('m3').addEntity('e3').addNamedProperty('p3')
  })

  test('returns false when called on a property not in an entity', ({ assert }) => {
    const p4 = new DataProperty(root)
    const result = p4.isChildOf('some-key')
    assert.isFalse(result)
  })

  test('returns false when called on self', ({ assert }) => {
    const result = p2.isChildOf(p2.key)
    assert.isFalse(result)
  })

  test('returns true when called on a direct parent', ({ assert }) => {
    const result = p2.isChildOf(e2.key)
    assert.isTrue(result)
  })

  test('returns true when called on a grandparent', ({ assert }) => {
    const result = p3.isChildOf(root.key)
    assert.isTrue(result)
  })

  test('returns false when called on a sibling', ({ assert }) => {
    const p4 = e1.addNamedProperty('p4')
    const result = p1.isChildOf(p4.key)
    assert.isFalse(result)
  })

  test('returns false when called on a child', ({ assert }) => {
    const result = e2.isChildOf(p2.key)
    assert.isFalse(result)
  })

  test('returns false when called on a non-existent namespace', ({ assert }) => {
    const result = p2.isChildOf('non-existent-key')
    assert.isFalse(result)
  })

  test('returns true when called on a grandparent', ({ assert }) => {
    const result = p3.isChildOf(n1.key)
    assert.isTrue(result)
  })

  test('returns true when called on a data model in the same namespace', ({ assert }) => {
    const result = p1.isChildOf(m1.key)
    assert.isTrue(result)
  })
})
