...
 
Commits (23)
......@@ -3,7 +3,7 @@ import sleep from '../../src/common/helpers/sleep';
export default async(driver) => {
// select first image
const firstImage = await driver.waitForElementByAccessibilityId('Gallery Image 0', wd.asserters.isDisplayed, 5000);
const firstImage = await driver.waitForElementByAccessibilityId('Gallery image/jpeg', wd.asserters.isDisplayed, 5000);
await firstImage.click();
await sleep(3000);
......
......@@ -42,7 +42,7 @@ describe('Discovery post edit flow', () => {
it('should search for the post', async () => {
// select all list
const all = await driver.waitForElementByAccessibilityId('Discovery All', wd.asserters.isDisplayed, 1000);
const all = await driver.waitForElementByAccessibilityId('Discovery All', wd.asserters.isDisplayed, 5000);
await all.click();
await sleep(500);
......
......@@ -66,7 +66,7 @@ describe('Post flow tests', () => {
await driver.waitForElementByAccessibilityId('Newsfeed Screen', wd.asserters.isDisplayed, 10000);
// the first element of the list should be the post
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[1]');
const textElement = await driver.waitForElementByXPath('//android.view.ViewGroup[@content-desc="Newsfeed Screen"]/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.ViewGroup/android.widget.TextView[2]');
expect(await textElement.text()).toBe(str);
});
......@@ -138,7 +138,7 @@ describe('Post flow tests', () => {
// get the Image touchable
const imageButton = await driver.waitForElementByAccessibilityId('Posted Image', wd.asserters.isDisplayed, 10000);
await imageButton.click();
const image = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.widget.ImageView');
const image = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.widget.ImageView');
const goBack = await driver.waitForElementByAccessibilityId('Go back button', wd.asserters.isDisplayed, 10000);
await goBack.click();
......
......@@ -27,9 +27,10 @@ describe('Top-bar tests', () => {
});
it('should open the boost console', async () => {
const button = await driver.waitForElementByAccessibilityId('boost-console button', wd.asserters.isDisplayed, 5000);
const button = await driver.waitForElementByAccessibilityId('boost-console button', wd.asserters.isDisplayed, 7000);
await button.click();
const textElement = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[2]/android.widget.TextView');
const textElement = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[2]/android.widget.TextView');
expect(await textElement.text()).toBe('Boost Console');
const back = await driver.waitForElementByXPath('//android.widget.Button[@content-desc="Go back"]/android.view.ViewGroup/android.widget.ImageView');
......@@ -39,10 +40,9 @@ describe('Top-bar tests', () => {
it('should open the users profile on clicking the profile avatar', async () => {
const button = await driver.waitForElementByAccessibilityId('topbar avatar button', wd.asserters.isDisplayed, 5000);
await button.click();
await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[1]/android.widget.ImageView');
await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[3]/android.widget.ImageView');
const back = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[2]/android.widget.TextView')
back.click();
const back = await driver.waitForElementByXPath('/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup[1]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.ViewGroup[2]/android.widget.TextView'); back.click();
});
it('should open the menu when clicking the hamburger menu', async () => {
......
......@@ -39,7 +39,7 @@ exports[`blog card component should renders correctly 1`] = `
source={
Object {
"headers": Object {
"App-Version": "3.8.0-rc1",
"App-Version": "3.8.0",
"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache",
},
......
......@@ -165,7 +165,7 @@ exports[`blog view screen component should renders correctly 1`] = `
source={
Object {
"headers": Object {
"App-Version": "3.8.0-rc1",
"App-Version": "3.8.0",
"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache",
},
......
......@@ -13,6 +13,7 @@ jest.mock('../../src/channel/ChannelStore');
jest.mock('../../src/channel/UserModel');
jest.mock('../../src/common/services/features.service');
jest.mock('../../AppStores');
jest.mock('../../src/common/services/boosted-content.service');
/**
* Tests
......
......@@ -38,6 +38,7 @@ jest.mock('../../src/common/components/FeedList', () => 'FeedList');
jest.mock('../../src/capture/CaptureFab', () => 'CaptureFab');
jest.mock('../../src/blogs/BlogCard', () => 'BlogCard');
jest.mock('../../src/common/components/Touchable', () => 'Touchable');
jest.mock('../../src/common/services/boosted-content.service');
/**
* Tests
......
......@@ -14,7 +14,7 @@ import appStores from '../../../AppStores';
jest.mock('../../../src/auth/UserStore');
jest.mock('../../../AppStores');
jest.mock('../../../src/channel/ChannelStore');
jest.mock('../../../src/common/services/boosted-content.service');
jest.mock('TouchableHighlight', () => 'TouchableHighlight');
/**
......
......@@ -32,7 +32,7 @@ exports[`channel header component owner should render correctly 1`] = `
source={
Object {
"headers": Object {
"App-Version": "3.8.0-rc1",
"App-Version": "3.8.0",
"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache",
},
......
import boostedContentService from "../../../src/common/services/boosted-content.service";
import FeedsService from "../../../src/common/services/feeds.service";
jest.mock('../../../src/common/services/feeds.service');
jest.mock('../../../src/common/services/session.service');
/**
* Tests
*/
describe('Boosted content service', () => {
it('should fetch the boosts from the server', async () => {
const fakeBoosts = [{guid: 1}, {guid: 2}, {guid: 3}];
boostedContentService.feedsService.getEntities.mockResolvedValue(fakeBoosts);
// load the boosts
await boostedContentService.load();
// should fetch the feed
expect(boostedContentService.feedsService.setEndpoint).toBeCalledWith('api/v2/boost/feed');
expect(boostedContentService.feedsService.setOffset).toBeCalledWith(0);
expect(boostedContentService.feedsService.setLimit).toBeCalledWith(12);
expect(boostedContentService.feedsService.fetch).toBeCalled();
// should fetch the boosts entities
expect(boostedContentService.feedsService.getEntities).toBeCalled();
// the boosts should be stored in the boosts property
expect(boostedContentService.boosts).toBe(fakeBoosts);
});
it('should return next boost and start again when the end is reached', () => {
const fakeBoosts = [{guid: 1}, {guid: 2}, {guid: 3}];
boostedContentService.boosts = fakeBoosts;
// next
expect(boostedContentService.fetch()).toBe(fakeBoosts[0]);
// next
expect(boostedContentService.fetch()).toBe(fakeBoosts[1]);
// next
expect(boostedContentService.fetch()).toBe(fakeBoosts[2]);
// start again
expect(boostedContentService.fetch()).toBe(fakeBoosts[0]);
});
});
\ No newline at end of file
......@@ -15,6 +15,7 @@ configure({ adapter: new Adapter() });
jest.mock('react-native-localize');
jest.mock('mobx-react/native', () => require('mobx-react/custom'));
jest.mock('./AppStores');
jest.useFakeTimers();
......
......@@ -47,6 +47,10 @@ export default class Boost extends Component {
renderEntity() {
const entity = this.props.boost.entity;
if (!entity) {
return null;
}
switch (entity.type) {
case 'activity':
return <Activity entity={ActivityModel.create(entity)} hideTabs={true} navigation={this.props.navigation} />;
......
......@@ -132,7 +132,7 @@ export default class CaptureGallery extends PureComponent {
})
}
}
{...testID(`Gallery Image ${index}`)}
{...testID(`Gallery ${node.type}`)}
>
<Image
source={{ uri : node.image.uri }}
......
......@@ -413,8 +413,6 @@ export default class CapturePoster extends Component {
onNsfw = values => {
const nsfw = [...values];
this.setState({ nsfw });
creatorNsfwService.set(nsfw);
}
onShare = network => {
......
export default {
load: jest.fn(),
fetch: jest.fn(),
}
\ No newline at end of file
const feedService = function() {
this.getEntities = jest.fn();
this.prepend = jest.fn();
this.setInjectBoost = jest.fn().mockImplementation(() => this);
this.setLimit = jest.fn().mockImplementation(() => this);
this.setOffset = jest.fn().mockImplementation(() => this);
this.setEndpoint = jest.fn().mockImplementation(() => this);
this.setParams = jest.fn().mockImplementation(() => this);
this.setAsActivities = jest.fn().mockImplementation(() => this);
this.fetch = jest.fn();
this.fetchLocal = jest.fn();
this.fetchRemoteOrLocal = jest.fn();
this.fetchLocalOrRemote = jest.fn();
}
export default feedService;
\ No newline at end of file
......@@ -2,5 +2,6 @@ export default {
login: jest.fn(),
logout: jest.fn(),
isLoggedIn: jest.fn(),
setInitialScreen: jest.fn()
setInitialScreen: jest.fn(),
onLogin: jest.fn()
}
\ No newline at end of file
......@@ -30,12 +30,12 @@ class AttachmentService {
let promise;
if(file.type.includes('video')){
promise = this.uploadToS3(file,progress);
if (file.type.includes('video')) {
promise = this.uploadToS3(file, progress);
} else {
promise = api.upload('api/v1/media/', file, extra, progress);
}
return promise;
}
......@@ -44,31 +44,29 @@ class AttachmentService {
* 1) prepare request return lease with signed url
* 2) upload file to S3 with signed url
* 3) complete upload
* @param {any} file
* @param {function} progress
* @param {any} file
* @param {function} progress
*/
uploadToS3(file, progress){
// Prepare media and wait for lease => {media_type, guid}
let lease;
return new Cancelable((resolve, reject, onCancel) => {
api.put(`api/v2/media/upload/prepare/video`).then((response) => {
lease = response.lease
return new Cancelable(async (resolve, reject, onCancel) => {
const response = await api.put(`api/v2/media/upload/prepare/video`);
// upload file to s3
const uploadPromise = api.uploadToS3(lease, file, progress).then(async () => {
const uploadPromise = api.uploadToS3(response.lease, file, progress).then(async () => {
// complete upload and wait for status
const {status} = await api.put(`api/v2/media/upload/complete/${lease.media_type}/${lease.guid}`);
const {status} = await api.put(`api/v2/media/upload/complete/${response.lease.media_type}/${response.lease.guid}`);
// if false is returned, upload fails message will be showed
return status === 'success' ? {guid: lease.guid} : false;
return status === 'success' ? {guid: response.lease.guid} : false;
});
// handle cancel
onCancel((cb) => {
uploadPromise.cancel();
cb();
});
return uploadPromise;
});
resolve(uploadPromise);
}).catch( error => {
if (error.name !== 'CancelationError') {
logService.exception('[ApiService] upload', error);
......
import apiService from "./api.service";
import FeedStore from "../stores/FeedStore";
import FeedsService from "./feeds.service";
import sessionService from "./session.service";
/**
* Boosted content service
*/
class BoostedContentService {
offset: number = -1;
feedStore = new FeedStore;
feedsService: FeedsService = new FeedsService;
boosts: Array<ActivityModel> = [];
/**
* Constructor
*/
constructor() {
// TODO: LOAD when session begin
// this.feedStore
// .setLimit(50)
// .setOffset(0)
// .setEndpoint('api/v2/boost/feed')
// .fetch();
// always reload on login or app restart
sessionService.onLogin(this.load);
}
fetch() {
//TODO: IMPLEMENT
/**
* Reload boosts list
*/
load = async() => {
await this.feedsService
.setLimit(12)
.setOffset(0)
.setEndpoint('api/v2/boost/feed')
.fetch();
this.boosts = await this.feedsService.getEntities();
}
/**
* Fetch one boost
*/
fetch() {
this.offset++;
if (this.offset >= this.boosts.length) {
this.offset = 0;
}
return this.boosts[this.offset];
}
}
export default new BoostedContentService();
......@@ -8,6 +8,7 @@ import i18n from './i18n.service';
import connectivityService from './connectivity.service';
import Colors from '../../styles/Colors';
import { toJS } from 'mobx';
import boostedContentService from './boosted-content.service';
/**
* Feed store
......@@ -17,7 +18,12 @@ export default class FeedsService {
/**
* @var {boolean}
*/
asActivities = true;
injectBoost: boolean = false;
/**
* @var {boolean}
*/
asActivities: boolean = true;
/**
* @var {Number}
......@@ -37,19 +43,46 @@ export default class FeedsService {
/**
* @var {Object}
*/
params = {sync: 1}
params: Object = {sync: 1}
/**
* @var {Array}
*/
feed = [];
feed: Array = [];
/**
* Get entities from the current page
*/
async getEntities() {
const feedPage = this.feed.slice(this.offset, this.limit + this.offset);
return await entitiesService.getFromFeed(feedPage, this, this.asActivities);
const end = this.limit + this.offset;
const feedPage = this.feed.slice(this.offset, end);
const result = await entitiesService.getFromFeed(feedPage, this, this.asActivities);
if (!this.injectBoost) return result;
this.injectBoosted(3, result, end);
this.injectBoosted(8, result, end);
this.injectBoosted(16, result, end);
this.injectBoosted(24, result, end);
this.injectBoosted(32, result, end);
this.injectBoosted(40, result, end);
return result;
}
/**
* Inject boost at given position
*
* @param {number} position
* @param {Array<ActivityModel>} entities
* @param {number} end
*/
injectBoosted(position, entities, end) {
if (this.offset <= position && end >= position) {
const boost = boostedContentService.fetch();
if (boost) entities.splice( position + this.offset, 0, boost );
}
}
/**
......@@ -90,6 +123,16 @@ export default class FeedsService {
return this;
}
/**
* Set inject boost
* @param {Array} feed
* @returns {FeedsService}
*/
setInjectBoost(injectBoost): FeedsService {
this.injectBoost = injectBoost;
return this;
}
/**
* Set limit
* @param {integer} limit
......
......@@ -181,6 +181,16 @@ export default class FeedStore {
return this;
}
/**
* Set inject boost
* @param {Array} feed
* @returns {FeedStore}
*/
setInjectBoost(injectBoost): FeedStore {
this.feedsService.setInjectBoost(injectBoost);
return this;
}
/**
* Set the params for the feeds service
* @param {Object} params
......
export const Version = {
VERSION: '3.8.0-rc1',
BUILD: '20190723'
VERSION: '3.8.0',
BUILD: '20190806'
};
......@@ -66,46 +66,11 @@ class DiscoveryStore {
this.listenChanges();
this.listStore.getMetadataService()
.setSource('feed/discovery')
.setMedium('feed');
}
/**
* Inject boosts to the feed
* @param {object} feed
* @param {OffsetFeedListStore} list
*/
async injectBoosts(feed, list) {
const start = list.entities.length;
const finish = feed.entities.length + start;
if (finish > 40) return;
await this.insertBoost(3, feed, start, finish);
await this.insertBoost(8, feed, start, finish);
await this.insertBoost(16, feed, start, finish);
await this.insertBoost(24, feed, start, finish);
await this.insertBoost(32, feed, start, finish);
await this.insertBoost(40, feed, start, finish);
}
/**
* Insert a boost in give position
* @param {integer} position
* @param {object} feed
* @param {integer} start
* @param {integer} finish
*/
async insertBoost(position, feed, start, finish) {
if (start <= position && finish >= position) {
try {
const boost = await boostedContentService.fetch();
if (boost) feed.entities.splice( position + start, 0, boost );
} catch (err) {
logService.exception('[DiscoveryStore] insertBoost', err);
}
}
this.listStore
.setInjectBoost(true)
.getMetadataService()
.setSource('feed/discovery')
.setMedium('feed');
}
/**
......
......@@ -236,8 +236,8 @@ export default class ActivityModel extends BaseModel {
@action
async updateActivity(data = {}) {
const list = this._list;
delete(this._list);
const list = this.__list;
delete(this.__list);
const entity = toJS(this);
this._list = list;
......
......@@ -41,6 +41,7 @@ class NewsfeedStore {
this.buildStores();
this.feedStore
.setEndpoint(`api/v2/feeds/subscribed/activities`)
.setInjectBoost(true)
.setLimit(12);
}
......