Skip to content

Commit

Permalink
fix(BTable): Pass through original records to slots in most circumsta…
Browse files Browse the repository at this point in the history
…nces (#1869)
  • Loading branch information
dwgray committed Apr 30, 2024
1 parent 7d5fbe6 commit bd5541f
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 51 deletions.
6 changes: 5 additions & 1 deletion packages/bootstrap-vue-next/src/components/BTable/BTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,11 @@ const computedItems = computed<T[]>(() => {
let mappedItems = usesProvider.value ? internalItems.value : (props.items as T[])
mappedItems = mappedItems.map((item) => {
if (typeof item === 'object' && item !== null) {
if (
typeof item === 'object' &&
item !== null &&
Object.keys(item).some((key) => key.includes('.'))
) {
// We use any here because the TS doesn't isn't certain that "item" is the same type as our newItem.
// But we've determined that it's an object, so we can ignore it since they will always be the same "object"
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
32 changes: 26 additions & 6 deletions packages/bootstrap-vue-next/src/components/BTable/BTableLite.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
<slot
name="custom-body"
:fields="computedFields"
:items="internalItems"
:items="items"
:columns="computedFields.length"
>
<BTr v-if="!props.stacked && $slots['top-row']">
Expand Down Expand Up @@ -99,9 +99,9 @@
"
:value="get(item, String(field.key))"
:index="itemIndex"
:item="item"
:item="original(item)"
:field="field"
:items="internalItems"
:items="items"
:toggle-details="
() => {
toggleRowDetails(item)
Expand All @@ -122,7 +122,7 @@
<BTd :colspan="computedFieldsTotal">
<slot
name="row-details"
:item="item"
:item="original(item)"
:toggle-details="
() => {
toggleRowDetails(item)
Expand All @@ -137,7 +137,7 @@
</template>
<BTr v-if="props.showEmpty && internalItems.length === 0" class="b-table-empty-slot">
<BTd :colspan="computedFieldsTotal">
<slot name="empty" :items="internalItems">
<slot name="empty" :items="items">
{{ emptyText }}
</slot>
</BTd>
Expand Down Expand Up @@ -183,7 +183,7 @@
<slot
name="custom-foot"
:fields="computedFields"
:items="internalItems"
:items="items"
:columns="computedFields.length"
/>
</BTfoot>
Expand Down Expand Up @@ -273,6 +273,26 @@ watch(
}
)
const itemMap = computed(() => {
const map = new Map<string | number, T>()
const key = props.primaryKey
if (key) {
props.items.forEach((item) => {
if (isTableItem(item)) {
map.set(item[key] as string | number, item)
}
})
}
return map
})
const original = (item: T) => {
const key = props.primaryKey
return key
? itemMap.value.get((item as Record<string, unknown>)[key] as string | number) ?? item
: item
}
const computedTableClasses = computed(() => [
props.tableClass,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@ import {enableAutoUnmount, mount} from '@vue/test-utils'
import {afterEach, describe, expect, it} from 'vitest'
import BTableLite from './BTableLite.vue'

class Person {
constructor(
public id: number,
public firstName: string,
public lastName: string,
public age: number
) {
this.id = id
this.firstName = firstName
this.lastName = lastName
this.age = age
}
}

describe('btablelite', () => {
enableAutoUnmount(afterEach)

Expand Down Expand Up @@ -194,4 +208,63 @@ describe('btablelite', () => {

expect((wrapper.html().match(/THE ROW!/g) || []).length).toBe(2)
})

it('Passes the original object for scoped cell slot item', () => {
const items = [new Person(1, 'John', 'Doe', 30), new Person(2, 'Jane', 'Smith', 25)]
const wrapper = mount(BTableLite, {
props: {
primaryKey: 'id',
items,
},
slots: {
'cell()': `<template #cell()="row">{{ row.item.constructor.name }}</template>`,
},
})
const $tbody = wrapper.get('tbody')
const $tr = $tbody.findAll('tr')
$tr.forEach((el) => {
const $tds = el.findAll('td')
expect($tds.length).toBe(4)
$tds.forEach(($td) => {
expect($td.text()).toBe('Person')
})
})
})

it('Passes the original objects for scoped cell slot items', () => {
const items = [new Person(1, 'John', 'Doe', 30), new Person(2, 'Jane', 'Smith', 25)]
const wrapper = mount(BTableLite, {
props: {
primaryKey: 'id',
items,
},
slots: {
'cell()': `<template #cell()="row">{{ row.items[0].constructor.name }}</template>`,
},
})
const $tbody = wrapper.get('tbody')
const $tr = $tbody.findAll('tr')
$tr.forEach((el) => {
const $tds = el.findAll('td')
expect($tds.length).toBe(4)
$tds.forEach(($td) => {
expect($td.text()).toBe('Person')
})
})
})

it('Passes the original objects for scoped custom table body', () => {
const items = [new Person(1, 'John', 'Doe', 30), new Person(2, 'Jane', 'Smith', 25)]
const wrapper = mount(BTableLite, {
props: {
primaryKey: 'id',
items,
},
slots: {
'custom-body': `<template #custom-body="table">{{ table.items[0].constructor.name }}</template>`,
},
})
const $tbody = wrapper.get('tbody')
expect($tbody.text()).toBe('Person')
})
})
127 changes: 83 additions & 44 deletions packages/bootstrap-vue-next/src/components/BTable/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ const formattedFields: Exclude<TableField<SimplePerson>, string>[] = [
{key: 'age', label: 'Age', sortable: true},
]

class Person {
constructor(
public id: number,
public firstName: string,
public lastName: string,
public age: number
) {
this.id = id
this.firstName = firstName
this.lastName = lastName
this.age = age
}
}

const nestableItemsTest = {
items: [
{yoo1: 123, yoo2: 321},
Expand All @@ -51,9 +65,9 @@ const nestableItemsTest = {
] satisfies TableField[],
}

describe('tbody', () => {
enableAutoUnmount(afterEach)
enableAutoUnmount(afterEach)

describe('tbody', () => {
it('has table class by default', () => {
const wrapper = mount(BTable, {
props: {items: simpleItems},
Expand Down Expand Up @@ -94,6 +108,7 @@ describe('tbody', () => {
expect(heads[1].classes()).toContain('b-table-sortable-column')
})
})

describe('single-sort', () => {
it('does not show sortable columns when sortable undefined', () => {
const wrapper = mount(BTable, {
Expand Down Expand Up @@ -241,56 +256,80 @@ describe('single-sort', () => {
.map((row) => row.find('td').text())
expect(text).toStrictEqual(['Havij', 'Robert', 'Cyndi'])
})
})

describe('multi-sort', () => {
it('has aria-sort labels reflecting sortBy prop', () => {
const wrapper = mount(BTable, {
props: {
multisort: true,
items: multiSort,
fields: simpleFields,
sortBy: [
{key: 'age', order: 'asc'},
{key: 'first_name', order: 'desc'},
],
},
})
const heads = wrapper.get('table').findAll('th')
expect(heads[0].attributes('aria-sort')).toBe('descending')
expect(heads[1].attributes('aria-sort')).toBe('ascending')
})

it('correctly sorts on two columns', () => {
const wrapper = mount(BTable, {
props: {
multisort: true,
items: multiSort,
fields: simpleFields,
sortBy: [
{key: 'first_name', order: 'desc'},
{key: 'age', order: 'asc'},
],
},
})
const text = wrapper
.get('tbody')
.findAll('tr')
.map((row) => row.findAll('td')[1].text())
expect(text).toStrictEqual(['35', '42', '27', '9', '101'])
describe('multi-sort', () => {
it('has aria-sort labels reflecting sortBy prop', () => {
const wrapper = mount(BTable, {
props: {
multisort: true,
items: multiSort,
fields: simpleFields,
sortBy: [
{key: 'age', order: 'asc'},
{key: 'first_name', order: 'desc'},
],
},
})
const heads = wrapper.get('table').findAll('th')
expect(heads[0].attributes('aria-sort')).toBe('descending')
expect(heads[1].attributes('aria-sort')).toBe('ascending')
})

it('will display data when using a inferred nested object [item: { "yoo.1": 123, "yoo.2": 321 }]', () => {
it('correctly sorts on two columns', () => {
const wrapper = mount(BTable, {
props: nestableItemsTest,
props: {
multisort: true,
items: multiSort,
fields: simpleFields,
sortBy: [
{key: 'first_name', order: 'desc'},
{key: 'age', order: 'asc'},
],
},
})
const text = wrapper
.get('tbody')
.findAll('tr')
.map((row) => row.findAll('td').map((td) => td.text()))
expect(text).toStrictEqual([
['123', '321'],
['789', '987'],
])
.map((row) => row.findAll('td')[1].text())
expect(text).toStrictEqual(['35', '42', '27', '9', '101'])
})
})

it('will display data when using a inferred nested object [item: { "yoo.1": 123, "yoo.2": 321 }]', () => {
const wrapper = mount(BTable, {
props: nestableItemsTest,
})
const text = wrapper
.get('tbody')
.findAll('tr')
.map((row) => row.findAll('td').map((td) => td.text()))
expect(text).toStrictEqual([
['123', '321'],
['789', '987'],
])
})

describe('object-persistence', () => {
it('Passes the original object for scoped cell slot item', () => {
const items = [new Person(1, 'John', 'Doe', 30), new Person(2, 'Jane', 'Smith', 25)]
const wrapper = mount(BTable, {
props: {
primaryKey: 'id',
items,
},
slots: {
'cell()': `<template #cell()="row">{{ row.item.constructor.name }}</template>`,
},
})
const $tbody = wrapper.get('tbody')
const $tr = $tbody.findAll('tr')
$tr.forEach((el) => {
const $tds = el.findAll('td')
expect($tds.length).toBe(4)
$tds.forEach(($td) => {
expect($td.text()).toBe('Person')
})
})
})
})

0 comments on commit bd5541f

Please sign in to comment.