paginate by more button

add "load more" button on bottom of musician listing that renders the records prefetched
This commit is contained in:
Nuwan 2021-12-18 20:11:28 +05:30
parent 961183bd47
commit 505f2d5592
13 changed files with 965 additions and 151 deletions

View File

@ -0,0 +1,319 @@
{
"musicians": [
{
"id": "1",
"first_name": "Test",
"last_name": "User1",
"name": "Test User1",
"city": "Denver",
"state": "CO",
"country": "US",
"online": true,
"musician": true,
"photo_url": null,
"biography": "Biography of Test User1. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 },
{ "instrument_id": "keyboard", "description": "Keyboard", "proficiency_level": 3, "priority": 8 },
{ "instrument_id": "ukulele", "description": "Ukulele", "proficiency_level": 3, "priority": 11 },
{ "instrument_id": "voice", "description": "Voice", "proficiency_level": 3, "priority": 13 },
{ "instrument_id": "piano", "description": "Piano", "proficiency_level": 2, "priority": 10 },
{ "instrument_id": "banjo", "description": "Banjo", "proficiency_level": 2, "priority": 4}
],
"followings": [],
"is_friend": true,
"is_following": false,
"pending_friend_request": false,
"friend_count": 1,
"follow_count": 0,
"recording_count": 0,
"session_count": 10,
"audio_latency": 5,
"last_active_timestamp": 1629916641,
"genres": [
{
"description": "Asian"
},
{
"description": "Classical"
},
{
"description": "Hard Rock"
},
{
"description": "Jazz"
},
{
"description": "Latin"
},
{
"description": "Oldies"
},
{
"description": "Pop"
},
{
"description": "Soft Rock"
}
]
},
{
"id": "2",
"first_name": "Test",
"last_name": "User2",
"name": "Test User2",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "Biography of Test User2.",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "3",
"first_name": "Test",
"last_name": "User3",
"name": "Test User3",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "4",
"first_name": "Test",
"last_name": "User4",
"name": "Test User4",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "5",
"first_name": "Test",
"last_name": "User5",
"name": "Test User5",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "6",
"first_name": "Test",
"last_name": "User6",
"name": "Test User6",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "7",
"first_name": "Test",
"last_name": "User7",
"name": "Test User7",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "8",
"first_name": "Test",
"last_name": "User8",
"name": "Test User8",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "9",
"first_name": "Test",
"last_name": "User9",
"name": "Test User9",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "10",
"first_name": "Test",
"last_name": "User10",
"name": "Test User10",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
}
],
"page_count": 2,
"my_audio_latency": 5,
"filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}",
"description": "Current Search: Sort = Latency to Me",
"is_blank_filter": false
}

View File

@ -0,0 +1,293 @@
{
"musicians": [
{
"id": "11",
"first_name": "Test",
"last_name": "User11",
"name": "Test User11",
"city": "Denver",
"state": "CO",
"country": "US",
"online": true,
"musician": true,
"photo_url": null,
"biography": "Biography of Test User1",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 },
{ "instrument_id": "keyboard", "description": "Keyboard", "proficiency_level": 3, "priority": 8 },
{ "instrument_id": "ukulele", "description": "Ukulele", "proficiency_level": 3, "priority": 11 },
{ "instrument_id": "voice", "description": "Voice", "proficiency_level": 3, "priority": 13 },
{ "instrument_id": "piano", "description": "Piano", "proficiency_level": 2, "priority": 10 }
],
"followings": [],
"is_friend": true,
"is_following": false,
"pending_friend_request": false,
"friend_count": 1,
"follow_count": 0,
"recording_count": 0,
"session_count": 10,
"audio_latency": 5,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "12",
"first_name": "Test",
"last_name": "User12",
"name": "Test User12",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "13",
"first_name": "Test",
"last_name": "User13",
"name": "Test User13",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "14",
"first_name": "Test",
"last_name": "User14",
"name": "Test User14",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "15",
"first_name": "Test",
"last_name": "User15",
"name": "Test User15",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "16",
"first_name": "Test",
"last_name": "User16",
"name": "Test User16",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "17",
"first_name": "Test",
"last_name": "User17",
"name": "Test User17",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "18",
"first_name": "Test",
"last_name": "User18",
"name": "Test User18",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "19",
"first_name": "Test",
"last_name": "User19",
"name": "Test User19",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
},
{
"id": "20",
"first_name": "Test",
"last_name": "User20",
"name": "Test User20",
"city": "Austin",
"state": "TX",
"country": "US",
"online": false,
"musician": true,
"photo_url": null,
"biography": "",
"full_score": null,
"instruments": [
{ "instrument_id": "acoustic guitar", "description": "Acoustic Guitar", "proficiency_level": 3, "priority": 1 }
],
"followings": [],
"is_friend": false,
"is_following": false,
"pending_friend_request": false,
"friend_count": 0,
"follow_count": 0,
"recording_count": 0,
"session_count": 0,
"audio_latency": null,
"last_active_timestamp": 1629916641,
"genres": []
}
],
"page_count": 2,
"my_audio_latency": 5,
"filter_json": "{\"id\":\"68dcc055-cb5d-40d6-8ed4-66772d1a1a31\",\"user_id\":\"27bd4a30-d1b8-4eea-8454-01a104d59381\",\"foreign_key1_id\":null,\"data_blob\":{\"sort_order\":\"latency\",\"instruments\":[],\"genres\":[],\"concert_gigs\":\"-1\",\"interests\":\"any\",\"studio_sessions\":\"-1\",\"ages\":[],\"skill_level\":\"-1\"}}",
"description": "Current Search: Sort = Latency to Me",
"is_blank_filter": false
}

View File

@ -55,7 +55,8 @@ describe('Friends page without data', () => {
describe('Friends page with data', () => {
beforeEach(() => {
cy.stubAuthenticate({ id: '2' }); //currentUser id is 2 - people.yaml fixture
cy.intercept('POST', /\S+\/filter/, { fixture: 'people' }).as('getPeople');
cy.intercept('POST', /\S+\/filter\?page=1/, { fixture: 'people_page1' }).as('getPeople_page1');
cy.intercept('POST', /\S+\/filter\?page=2/, { fixture: 'people_page2' }).as('getPeople_page2');
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
});
@ -74,7 +75,7 @@ describe('Friends page with data', () => {
cy.contains('Update Search').should('exist');
cy.contains('Reset Filters').should('exist');
cy.get('[data-testid=peopleListTable] > tbody tr')
.should('have.length', 20)
.should('have.length', 10)
.first()
.contains('Test User1');
});
@ -115,8 +116,8 @@ describe('Friends page with data', () => {
.within(() => {
cy.contains('More').click();
});
showSidePanelContent();
closeSidePanel();
showSidePanelContent();
closeSidePanel();
});
it('click instruments more link', () => {
@ -145,7 +146,14 @@ describe('Friends page with data', () => {
showSidePanelContent();
closeSidePanel();
});
})
it('paginate', () => {
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 10);
cy.get('[data-testid=paginate-next-page]').click();
cy.get('[data-testid=peopleListTable] > tbody tr').should('have.length', 20);
cy.get('[data-testid=paginate-next-page]').should('not.exist');
});
});
describe('in mobile', () => {
beforeEach(() => {
@ -153,28 +161,48 @@ describe('Friends page with data', () => {
});
it('show profile', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').should('have.length', 20)
cy.get('[data-testid=peopleSwiper] .swiper-slide').eq(0).contains('Test User1')
cy.get('[data-testid=peopleSwiper] .swiper-slide').eq(0).should('be.visible')
cy.get('[data-testid=peopleSwiper] .swiper-slide').eq(2).should('not.be.visible')
})
cy.get('[data-testid=peopleSwiper] .swiper-slide').should('have.length', 10);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.contains('Test User1');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(0)
.should('be.visible');
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.eq(2)
.should('not.be.visible');
});
it('show all profile description', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').first()
.find('[data-testid=mobBiography]').should('not.contain', 'More')
})
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=mobBiography]')
.should('not.contain', 'More');
});
it('show all instruments', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').first().find('[data-testid=instrumentList] div').its('length').should('be.gte', 1)
cy.get('[data-testid=peopleSwiper] .swiper-slide').first()
.find('[data-testid=instrumentList]').should('not.contain', 'More')
})
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=instrumentList]')
.should('not.contain', 'More');
});
it('show all genres', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide').first().find('[data-testid=genreList] div').its('length').should('be.gte', 1)
cy.get('[data-testid=peopleSwiper] .swiper-slide').first()
.find('[data-testid=genreList]').should('not.contain', 'More')
})
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList] div')
.its('length')
.should('be.gte', 1);
cy.get('[data-testid=peopleSwiper] .swiper-slide')
.first()
.find('[data-testid=genreList]')
.should('not.contain', 'More');
});
it('click more button', () => {
cy.get('[data-testid=peopleSwiper] .swiper-slide')
@ -183,18 +211,23 @@ describe('Friends page with data', () => {
.click();
showSidePanelContent();
closeSidePanel();
})
});
it('click connect button', () => {})
it('click connect button', () => {});
it('click message button', () => {})
it('click message button', () => {});
})
//TODO: paginate
it('paginate', () => {
cy.get('[data-testid=peopleSwiper] .swiper-button-prev').should('have.class', 'swiper-button-disabled');
for (let i = 0; i < 19; i++) {
cy.get('[data-testid=peopleSwiper] .swiper-button-next').click();
cy.wait(500);
}
cy.get('[data-testid=peopleSwiper] .swiper-button-next').should('have.class', 'swiper-button-disabled');
});
});
});
describe('making friendship', () => {
it('add friend', () => {
cy.intercept('GET', /\S+\/profile\S+/, { fixture: 'person' });
@ -397,11 +430,20 @@ describe('Friends page with data', () => {
it('render filter form', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
cy.get('[data-testid=modalUpdateSearch]').within(() => {
cy.get('input[name=latency_good]').should('be.checked').next().contains('Good (less than 40ms)')
cy.get('input[name=latency_fair]').should('be.checked').next().contains('Fair (40-60ms)')
cy.get('input[name=latency_high]').should('not.be.checked').next().contains('Poor (more than 60ms)')
})
})
cy.get('input[name=latency_good]')
.should('be.checked')
.next()
.contains('Good (less than 40ms)');
cy.get('input[name=latency_fair]')
.should('be.checked')
.next()
.contains('Fair (40-60ms)');
cy.get('input[name=latency_high]')
.should('not.be.checked')
.next()
.contains('Poor (more than 60ms)');
});
});
it('reset filters', () => {
cy.get('[data-testid=btnUpdateSearch]').click();
@ -421,20 +463,25 @@ describe('Friends page with data', () => {
it('submit filter form with params', () => {
//wait for stubbed request sent to fetch data for initial page load
cy.wait('@getPeople').then(interception => {
cy.wait('@getPeople_page1').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data');
});
//the subsequent request sent to perfetch data and store in redux prefetched buffer
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '1st API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
cy.wait(1000)
cy.wait(1000);
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form without filling any form field
cy.wait('@getPeople')
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '2nd API call has data');
assert.isNotNull(interception.response.body, '3rd API call has data');
})
.its('request.body')
.should('deep.equal', {
.should('deep.contain', {
latency_good: true,
latency_fair: true,
latency_high: false,
@ -445,31 +492,34 @@ describe('Friends page with data', () => {
genres: []
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '4th API call has data - (prefethed)');
});
cy.get('[data-testid=btnUpdateSearch]').click();
fillFilterForm();
fillFilterForm(); // change filter options
cy.get('[data-testid=btnSubmitSearch]').click();
//wait for stubbed request sent by submitting search form again. but this time fill form fields
cy.wait('@getPeople')
cy.wait('@getPeople_page1')
.then(interception => {
assert.isNotNull(interception.response.body, '3rd API call has data');
cy.log(interception.request.body);
assert.isNotNull(interception.response.body, '5th API call has data');
})
.its('request.body')
//NOTE: for some reason I can not get following passed. But this works correctly in manual test
// .should('deep.equal', {
// latency_good: false,
// latency_fair: false,
// latency_high: false,
// proficiency_beginner: false,
// proficiency_intermediate: false,
// proficiency_expert: false,
// instruments: [{ value: 'drums', label: 'Drums' }],
// genres: ['pop'],
// active_within_days: '1',
// joined_within_days: '7'
// });
.should('deep.contain', {
latency_good: false,
latency_fair: false,
latency_high: false,
proficiency_beginner: false,
proficiency_intermediate: false,
proficiency_expert: false,
instruments: [{ value: 'drums', label: 'Drums' }],
genres: ['pop']
});
cy.wait('@getPeople_page2').then(interception => {
assert.isNotNull(interception.response.body, '6th API call has data - (prefethed)');
});
});
});
});

View File

@ -0,0 +1,33 @@
import React, { useRef } from "react";
import { useInfiniteLoading } from "../../hooks/useInfiniteLoading";
const JKMusicianList = ({ onPageChange }) => {
const { items, hasNext, hasPrevious, loadNext, loadPrevious } = useInfiniteLoading({
getItems: ({ page }) => {
/* Call API endpoint */
}
});
return (
<div>
{hasPrevious &&
<button onClick={() => loadPrevious()}>Load Previous</button>
}
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
</li>
))}
</ul>
{hasNext &&
<button onClick={() =>loadNext()}>Load More</button>
}
</div>
)
}
export default JKMusicianList;

View File

@ -2,14 +2,8 @@ import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { Alert, Card, CardBody, Col, Row, Button, Form } from 'reactstrap';
import Loader from '../common/Loader';
//import FalconCardHeader from '../common/FalconCardHeader';
import { isIterableArray } from '../../helpers/utils';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPeople } from '../../store/features/peopleSlice';
//import JKPeopleSearch from './JKPeopleSearch';
import { useSelector } from 'react-redux';
import JKPeopleList from './JKPeopleList';
import JKPeopleSwiper from './JKPeopleSwiper';
import { useResponsive } from '@farfetch/react-context-responsive';
@ -17,9 +11,8 @@ import { useResponsive } from '@farfetch/react-context-responsive';
const JKPeople = ({ className, onPageChange }) => {
const [page, setPage] = useState(1);
const [hasNext, setHasNext] = useState(false)
const peopleListRef = useRef();
const dispatch = useDispatch();
const { t } = useTranslation();
const people = useSelector(state => state.people.people);
const totalPages = useSelector(state => state.people.totalPages);
@ -27,29 +20,42 @@ const JKPeople = ({ className, onPageChange }) => {
const { greaterThan } = useResponsive();
const loadPeople = React.useCallback(() => {
if (totalPages !== 0 && page > totalPages) {
setPage(totalPages + 1);
return;
}
try {
console.log('BEFORE fetching people');
//dispatch(fetchPeople({ page }));
onPageChange(page)
} catch (error) {
console.log('Error fetching people', error);
}
}, [page, totalPages, dispatch]);
// const loadPeople = React.useCallback(() => {
// if (totalPages !== 0 && page > totalPages) {
// setPage(totalPages + 1);
// return;
// }
// try {
// onPageChange(page)
// } catch (error) {
// console.log('Error fetching people', error);
// }
// }, [page, totalPages, dispatch]);
useEffect(() => {
loadPeople();
}, [page]);
// useEffect(() => {
// loadPeople();
// }, [page]);
// useEffect(() => {
// if (loadingStatus === 'succeeded' && peopleListRef.current && page !== 1) {
// }
// }, [loadingStatus]);
useEffect(() => {
try {
console.log("DEBUG======", page, hasNext);
onPageChange(page, hasNext)
} catch (error) {
console.log('Error fetching people', error);
}
}, [page]);
useEffect(() => {
setHasNext(page < totalPages)
}, [totalPages, page])
const goNextPage = () => {
setPage(val => ++val);
};
@ -60,18 +66,18 @@ const JKPeople = ({ className, onPageChange }) => {
}
};
const handleScroll = () => {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
goNextPage();
}
};
// const handleScroll = () => {
// if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
// goNextPage();
// }
// };
useEffect(() => {
window.addEventListener('scroll', handleScroll, { passive: true });
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
// useEffect(() => {
// window.addEventListener('scroll', handleScroll, { passive: true });
// return () => {
// window.removeEventListener('scroll', handleScroll);
// };
// }, []);
return (
<div>
@ -83,7 +89,7 @@ const JKPeople = ({ className, onPageChange }) => {
{greaterThan.xs ? (
<Row className="mb-3 justify-content-between d-none d-md-block">
<div className="table-responsive-xl px-2" ref={peopleListRef}>
<JKPeopleList people={people} />
<JKPeopleList people={people} goNextPage={goNextPage} hasNext={hasNext} />
{loadingStatus === 'loading' && people.length !== 0 && <span>loading...</span>}
</div>
</Row>

View File

@ -1,24 +1,25 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { Button, Card, CardBody, Form, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import FalconCardHeader from '../common/FalconCardHeader';
import { useTranslation } from 'react-i18next';
import Select from 'react-select';
import JKTooltip from '../common/JKTooltip';
import PropTypes from 'prop-types';
import { getGenres, getInstruments } from '../../helpers/rest';
import { useForm, Controller, useFormState } from 'react-hook-form';
import { useDispatch } from 'react-redux';
import { fetchPeople, resetState } from '../../store/features/peopleSlice';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPeople, resetState, loadPrefetched, preFetchPeople } from '../../store/features/peopleSlice';
import JKPeople from './JKPeople';
function JKPeopleFilter() {
const { t } = useTranslation();
const [ show, setShow ] = useState(false);
const [resetFilter, setResetFilter] = useState(false);
const [page, setPage] = useState(1);
const [instruments, setInstruments] = useState([]);
const [genres, setGenres] = useState([]);
const dispatch = useDispatch();
const pageToRequest = useRef(1)
const totalPages = useSelector(state => state.people.totalPages);
const { register, handleSubmit, setValue, control } = useForm({
defaultValues: {
@ -86,6 +87,7 @@ function JKPeopleFilter() {
clearFilterOpts();
setResetFilter(false);
dispatch(resetState());
pageToRequest.current = 1
handleSubmit(onSubmit)()
}
}, [resetFilter]);
@ -111,22 +113,22 @@ function JKPeopleFilter() {
const submitForm = event => {
event.preventDefault();
pageToRequest.current = 1
dispatch(resetState());
handleSubmit(onSubmit)();
handleSubmit(onSubmit)(pageToRequest.current);
setShow(false);
};
const submitPageQuery = page => {
setPage(page)
handleSubmit(onSubmit)()
const submitPageQuery = (page, hasNextPage) => {
handleSubmit(onSubmit)(page, hasNextPage)
}
const onSubmit = data => {
setPage(1)
const onSubmit = (data, page, hasNextPage = true) => {
let _page = pageToRequest.current > page ? pageToRequest.current : page
let genres = [];
let joined_within_days,
active_within_days = '';
let joined_within_days = '';
let active_within_days = '';
if (data.genres) {
genres = data.genres.map(genre => genre.value);
@ -138,13 +140,24 @@ function JKPeopleFilter() {
active_within_days = data.active_within_days.value;
}
const updatedData = { ...data, genres, joined_within_days, active_within_days };
const params = { ...data, genres, joined_within_days, active_within_days };
try {
dispatch(fetchPeople({ data: updatedData, page: page }));
dispatch(loadPrefetched())
if(totalPages && _page >= totalPages){
return
}
dispatch(fetchPeople({ data: params, page: _page }));
pageToRequest.current++
if(hasNextPage){
dispatch(preFetchPeople({ data: params, page: _page + 1 }))
pageToRequest.current = pageToRequest.current + 2
}
} catch (error) {
console.log('Error fetching people', error);
console.log('error fetching people', error);
}
};
const lastActiveOpts = [
@ -347,19 +360,10 @@ function JKPeopleFilter() {
</ModalFooter>
</Modal>
<JKPeople onPageChange={submitPageQuery} />
</CardBody>
</Card>
);
}
JKPeopleFilter.propTypes = {
//show: PropTypes.bool,
//setShow: PropTypes.func
//setPeople: PropTypes.func
};
JKPeopleFilter.defaultProps = {
//show: false
};
export default JKPeopleFilter;

View File

@ -0,0 +1,38 @@
import React from 'react';
import { Table } from 'reactstrap';
import JKPerson from './JKPerson';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
const JKPeopleList = ({ people }) => {
const { t } = useTranslation();
return (
<Table striped bordered className="fs--1" data-testid="peopleListTable">
<thead className="bg-200 text-900">
<tr>
<th scope="col">{t('person_attributes.name', { ns: 'people' })}</th>
<th scope="col" style={{ minWidth: 250 }}>
{t('person_attributes.about', { ns: 'people' })}
</th>
<th scope="col">{t('person_attributes.instruments', { ns: 'people' })}</th>
<th scope="col">{t('person_attributes.genres', { ns: 'people' })}</th>
<th scope="col">{t('actions', { ns: 'common' })}</th>
</tr>
</thead>
<tbody className="list">
{people.map((person) => (
// <tr className="align-middle" key={`people-list-item-${person.id}`}>
<JKPerson person={person} viewMode="list" key={`jk-person-${person.id}`} />
// </tr>
))}
</tbody>
</Table>
);
};
JKPeopleList.propTypes = {
people: PropTypes.arrayOf(PropTypes.instanceOf(Object))
};
export default JKPeopleList;

View File

@ -1,33 +1,33 @@
import React from 'react';
import { Table } from 'reactstrap';
import { Table, Button } from 'reactstrap';
import JKPerson from './JKPerson';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
const JKPeopleList = ({ people }) => {
const JKPeopleList = ({ people, goNextPage, hasNext }) => {
const { t } = useTranslation();
return (
<Table striped bordered className="fs--1" data-testid="peopleListTable">
<thead className="bg-200 text-900">
<tr>
<th scope="col">{t('person_attributes.name', { ns: 'people' })}</th>
<th scope="col" style={{ minWidth: 250 }}>
{t('person_attributes.about', { ns: 'people' })}
</th>
<th scope="col">{t('person_attributes.instruments', { ns: 'people' })}</th>
<th scope="col">{t('person_attributes.genres', { ns: 'people' })}</th>
<th scope="col">{t('actions', { ns: 'common' })}</th>
</tr>
</thead>
<tbody className="list">
{people.map((person) => (
// <tr className="align-middle" key={`people-list-item-${person.id}`}>
<>
<Table striped bordered className="fs--1" data-testid="peopleListTable">
<thead className="bg-200 text-900">
<tr>
<th scope="col">{t('person_attributes.name', { ns: 'people' })}</th>
<th scope="col" style={{ minWidth: 250 }}>
{t('person_attributes.about', { ns: 'people' })}
</th>
<th scope="col">{t('person_attributes.instruments', { ns: 'people' })}</th>
<th scope="col">{t('person_attributes.genres', { ns: 'people' })}</th>
<th scope="col">{t('actions', { ns: 'common' })}</th>
</tr>
</thead>
<tbody className="list">
{people.map(person => (
<JKPerson person={person} viewMode="list" key={`jk-person-${person.id}`} />
// </tr>
))}
</tbody>
</Table>
))}
</tbody>
</Table>
{hasNext && <Button color="primary" outline={true} onClick={() => goNextPage()} data-testid="paginate-next-page">Load More</Button>}
</>
);
};

View File

@ -0,0 +1,56 @@
import {useState, useRef, useEffect} from 'react';
import { useHistory } from 'react-router-dom';
export const useInfiniteLoading = ((props) => {
const { getItems } = props;
const [items, setItems] = useState([]);
const initialPage = useRef(new URLSearchParams(window.location.search).get('page') || 1);
const lowestPageLoaded = useRef(initialPage.current);
const highestPageLoaded = useRef(initialPage.current);
const initialPageLoaded = useRef(false);
const [hasNext, setHasNext] = useState(true);
const [hasPrevious, setHasPrevious] = useState(() => initialPage.current > 1);
const history = useHistory();
const loadItems = async (page, itemCombineMethod) => {
const data = await getItems({ page });
setHasNext(data.totalPages > page)
setHasPrevious(page > 1)
setItems(prevItems => {
return itemCombineMethod === 'prepend' ?
[...data.items, ...prevItems] :
[...prevItems, ...data.items]
})
}
const loadNext = () => {
const nextPage = highestPageLoaded.current + 1;
history.replace(`?page=${nextPage}`);
loadItems(nextPage, 'append');
highestPageLoaded.current = nextPage;
}
const loadPrevoius = () => {
const nextPage = lowestPageLoaded.current - 1;
if (nextPage < 1) return;
history.replace(`?page=${nextPage}`);
loadItems(nextPage, 'prepend');
lowestPageLoaded.current = nextPage;
}
useEffect(() => {
if(initialPageLoaded.current){
return
}
loadItems(initialPage.current, 'append');
initialPageLoaded.current = true
}, [loadItems])
return{
items,
hasNext,
hasPrevious,
loadNext,
loadPrevoius
}
})

View File

@ -3,6 +3,7 @@ import { getPeople, getPersonById, acceptFriendRequest as accept } from '../../h
const initialState = {
people: [],
prefetched: [],
status: 'idel',
error: null,
totalPages: 0,
@ -16,6 +17,14 @@ export const fetchPeople = createAsyncThunk(
}
)
export const preFetchPeople = createAsyncThunk(
'people/preFetchPeople',
async (options, thunkAPI) => {
const response = await getPeople(options)
return response.json()
}
)
export const fetchPerson = createAsyncThunk(
'people/fetchPerson',
async (options, thunkAPI) => {
@ -56,6 +65,12 @@ export const peopleSlice = createSlice({
resetState: (state) => {
return { ...initialState }
},
loadPrefetched: (state, action) => {
if(state.prefetched.length > 0){
state.people = [...state.people, ...state.prefetched]
}
state.prefetched = []
}
},
extraReducers: (builder) => {
builder
@ -74,6 +89,9 @@ export const peopleSlice = createSlice({
state.status = 'failed'
state.error = action.error.message
})
.addCase(preFetchPeople.fulfilled, (state, action) => {
state.prefetched = action.payload.musicians
})
.addCase(acceptFriendRequest.fulfilled, (state, action) => {
})
@ -96,6 +114,6 @@ export const peopleSlice = createSlice({
export const selectPersonById = (state, userId) => state.people.find((person) => person.id === userId)
export const { add, resetState } = peopleSlice.actions;
export const { add, resetState, loadPrefetched } = peopleSlice.actions;
export default peopleSlice.reducer;

View File

@ -200,7 +200,7 @@ module JamRuby
rel
end
def search_results_page(filter=nil, page=1, user_ids = [])
def search_results_page(filter=nil, page=1, user_ids=nil)
if filter
self.data_blob = filter
self.save

View File

@ -153,9 +153,9 @@ module JamRuby
def do_search(params={}, user_ids)
rel = User.musicians.where('users.id <> ?', self.user.id)
#user_ids parameter is used to track users returned from neo4j user's latency data service. #if this variable is defined means we are calling this method to fiter musicians with latency data (currently this call only comes from api_search_controller#filter)
if defined?(user_ids)
#user_ids parameter is used to track users returned from neo4j user's latency data service. #if this variable is not null means we are calling this method to fiter musicians with neo4j latency data (currently this call only comes from api_search_controller#filter)
unless user_ids.nil?
if user_ids.empty?
return User.none # no user_ids have been passed from latency service. which means no results for the latency based filter conditions. So we return an empty result
else

View File

@ -119,8 +119,6 @@ class ApiSearchController < ApiController
user_ids = @latency_data.map{ |l_data| l_data[:user_id] }
#debugger
filter_params = {
"sort_order"=>"latency",
"instruments"=>[],
@ -160,10 +158,9 @@ class ApiSearchController < ApiController
filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank?
filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank?
#debugger
sobj = MusicianSearch.user_search_filter(current_user)
@search = sobj.search_results_page(filter_params, [params[:page].to_i, 1].max, user_ids)
#@search = sobj.search_results_page(filter_params, [params[:page].to_i, 1].max)
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index'