mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
1a31e69ec9 | |||
e065983c95 | |||
30a169171a | |||
8d1900362e | |||
e154189de1 | |||
b0f147de24 | |||
979b3548db | |||
05d6c578b3 | |||
a117559063 | |||
f87c649b09 | |||
6504dc9184 | |||
25a8ddffd4 | |||
fa0f9b89cf | |||
4d00a67891 | |||
bd2c43e1f4 | |||
c7279eaa34 | |||
fd523e552c | |||
cb877b8b23 | |||
ed1bee8b89 | |||
a8e1492056 | |||
5587216c01 | |||
86569261ad | |||
4a9049c7aa | |||
75d60a8182 | |||
14d4dc2ed9 | |||
fd0e1740a5 | |||
70ca890bef | |||
b9318dfd8e | |||
995642a719 | |||
d14de4ac9e | |||
b7f325a241 | |||
27c55718c2 | |||
421ff0654b | |||
ed947458f9 | |||
9cdb20ba84 |
@@ -7,6 +7,7 @@ namespace TweetDuck.Configuration{
|
||||
public const string ArgDataFolder = "-datafolder";
|
||||
public const string ArgLogging = "-log";
|
||||
public const string ArgIgnoreGDPR = "-nogdpr";
|
||||
public const string ArgNotificationScrollWA = "-nscrollwa";
|
||||
|
||||
// internal args
|
||||
public const string ArgRestart = "-restart";
|
||||
|
@@ -64,6 +64,10 @@ namespace TweetDuck.Core.Bridge{
|
||||
});
|
||||
}
|
||||
|
||||
public void SetRightClickedLink(string type, string url){
|
||||
ContextInfo.SetLink(type, url);
|
||||
}
|
||||
|
||||
public void SetRightClickedChirp(string tweetUrl, string quoteUrl, string chirpAuthors, string chirpImages){
|
||||
ContextInfo.SetChirp(tweetUrl, quoteUrl, chirpAuthors, chirpImages);
|
||||
}
|
||||
@@ -99,10 +103,6 @@ namespace TweetDuck.Core.Bridge{
|
||||
|
||||
// Global
|
||||
|
||||
public void SetLastRightClickInfo(string type, string url){
|
||||
ContextInfo.SetLink(type, url);
|
||||
}
|
||||
|
||||
public void OnTweetPopup(string columnId, string chirpId, string columnName, string tweetHtml, int tweetCharacters, string tweetUrl, string quoteUrl){
|
||||
notification.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
|
6
Core/FormBrowser.Designer.cs
generated
6
Core/FormBrowser.Designer.cs
generated
@@ -39,17 +39,17 @@
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = TweetDuck.Core.Utils.TwitterUtils.BackgroundColor;
|
||||
this.ClientSize = new System.Drawing.Size(400, 386);
|
||||
this.ClientSize = new System.Drawing.Size(1008, 730);
|
||||
this.Icon = Properties.Resources.icon;
|
||||
this.Location = TweetDuck.Core.Controls.ControlExtensions.InvisibleLocation;
|
||||
this.MinimumSize = new System.Drawing.Size(416, 424);
|
||||
this.MinimumSize = new System.Drawing.Size(348, 424);
|
||||
this.Name = "FormBrowser";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||
this.Activated += new System.EventHandler(this.FormBrowser_Activated);
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing);
|
||||
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed);
|
||||
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
|
||||
this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd);
|
||||
this.LocationChanged += new System.EventHandler(this.FormBrowser_LocationChanged);
|
||||
this.Resize += new System.EventHandler(this.FormBrowser_Resize);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
|
@@ -393,7 +393,7 @@ namespace TweetDuck.Core{
|
||||
FormSettings form = new FormSettings(this, plugins, updates, analytics, startTab);
|
||||
|
||||
form.FormClosed += (sender, args) => {
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck && !UpdateHandler.TemporarilyForceUpdateChecking){
|
||||
Config.DismissedUpdate = null;
|
||||
Config.Save();
|
||||
|
||||
|
@@ -196,6 +196,7 @@ namespace TweetDuck.Core.Handling{
|
||||
case MenuReadApplyROT13:
|
||||
string selection = parameters.SelectionText;
|
||||
control.InvokeAsyncSafe(() => FormMessage.Information("ROT13", StringUtils.ConvertRot13(selection), FormMessage.OK));
|
||||
control.InvokeAsyncSafe(analytics.AnalyticsFile.UsedROT13.Trigger);
|
||||
return true;
|
||||
|
||||
case MenuSearchInBrowser:
|
||||
|
@@ -4,11 +4,19 @@ using CefSharp.Enums;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class DragHandlerBrowser : IDragHandler{
|
||||
private readonly RequestHandlerBrowser requestHandler;
|
||||
|
||||
public DragHandlerBrowser(RequestHandlerBrowser requestHandler){
|
||||
this.requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
public bool OnDragEnter(IWebBrowser browserControl, IBrowser browser, IDragData dragData, DragOperationsMask mask){
|
||||
void TriggerDragStart(string type, string data = null){
|
||||
browserControl.ExecuteScriptAsync("window.TDGF_onGlobalDragStart", type, data);
|
||||
}
|
||||
|
||||
requestHandler.BlockNextUserNavUrl = dragData.LinkUrl; // empty if not a link
|
||||
|
||||
if (dragData.IsLink){
|
||||
TriggerDragStart("link", dragData.LinkUrl);
|
||||
}
|
||||
|
@@ -5,6 +5,12 @@ using TweetDuck.Core.Handling.General;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
class RequestHandlerBase : DefaultRequestHandler{
|
||||
private readonly bool autoReload;
|
||||
|
||||
public RequestHandlerBase(bool autoReload){
|
||||
this.autoReload = autoReload;
|
||||
}
|
||||
|
||||
public override bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture){
|
||||
return LifeSpanHandler.HandleLinkClick(browserControl, targetDisposition, targetUrl);
|
||||
}
|
||||
@@ -18,5 +24,11 @@ namespace TweetDuck.Core.Handling{
|
||||
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
if (autoReload){
|
||||
browser.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,14 @@
|
||||
using CefSharp;
|
||||
using System.Text.RegularExpressions;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
sealed class RequestHandlerBrowser : RequestHandlerBase{
|
||||
public override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status){
|
||||
browser.Reload();
|
||||
}
|
||||
private static readonly Regex TmpResourceRedirect = new Regex(@"dist\/(.*?)\.(.*?)\.js$", RegexOptions.Compiled);
|
||||
|
||||
public string BlockNextUserNavUrl { get; set; }
|
||||
|
||||
public RequestHandlerBrowser() : base(true){}
|
||||
|
||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback){
|
||||
if (request.ResourceType == ResourceType.Script && request.Url.Contains("analytics.")){
|
||||
@@ -16,11 +19,45 @@ namespace TweetDuck.Core.Handling{
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
public override bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect){
|
||||
if (userGesture && request.TransitionType == TransitionType.LinkClicked){
|
||||
bool block = request.Url == BlockNextUserNavUrl;
|
||||
BlockNextUserNavUrl = string.Empty;
|
||||
return block;
|
||||
}
|
||||
|
||||
return base.OnBeforeBrowse(browserControl, browser, frame, request, userGesture, isRedirect);
|
||||
}
|
||||
|
||||
public override bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response){
|
||||
if (request.ResourceType == ResourceType.Image && request.Url.Contains("backgrounds/spinner_blue")){
|
||||
request.Url = TwitterUtils.LoadingSpinner.Url;
|
||||
return true;
|
||||
}
|
||||
else if (request.ResourceType == ResourceType.Script){ // TODO delaying the apocalypse
|
||||
Match match = TmpResourceRedirect.Match(request.Url);
|
||||
|
||||
if (match.Success){
|
||||
string scriptType = match.Groups[1].Value;
|
||||
string scriptHash = match.Groups[2].Value;
|
||||
|
||||
const string HashBundle = "1bd75b5854";
|
||||
const string HashVendor = "942c0a20e8";
|
||||
|
||||
if (scriptType == "bundle" && scriptHash != HashBundle){
|
||||
request.Url = TmpResourceRedirect.Replace(request.Url, "dist/$1."+HashBundle+".js");
|
||||
System.Diagnostics.Debug.WriteLine("rewriting "+scriptType+" to "+request.Url);
|
||||
return true;
|
||||
}
|
||||
else if (scriptType == "vendor" && scriptHash != HashVendor){
|
||||
request.Url = TmpResourceRedirect.Replace(request.Url, "dist/$1."+HashVendor+".js");
|
||||
System.Diagnostics.Debug.WriteLine("rewriting "+scriptType+" to "+request.Url);
|
||||
return true;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("accepting "+scriptType+" as "+request.Url);
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnResourceResponse(browserControl, browser, frame, request, response);
|
||||
}
|
||||
|
@@ -124,7 +124,7 @@ namespace TweetDuck.Core.Notification{
|
||||
MenuHandler = new ContextMenuNotification(this, enableContextMenu),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBase()
|
||||
RequestHandler = new RequestHandlerBase(false)
|
||||
};
|
||||
|
||||
this.browser.Dock = DockStyle.None;
|
||||
|
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Bridge;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
@@ -136,7 +137,14 @@ namespace TweetDuck.Core.Notification{
|
||||
int eventType = wParam.ToInt32();
|
||||
|
||||
if (eventType == NativeMethods.WM_MOUSEWHEEL && IsCursorOverBrowser){
|
||||
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01), CefEventFlags.None);
|
||||
if (Arguments.HasFlag(Arguments.ArgNotificationScrollWA)){
|
||||
int delta = BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01);
|
||||
browser.ExecuteScriptAsync("window.scrollBy", 0, -Math.Round(delta/0.72));
|
||||
}
|
||||
else{
|
||||
browser.SendMouseWheelEvent(0, 0, 0, BrowserUtils.Scale(NativeMethods.GetMouseHookData(lParam), Program.UserConfig.NotificationScrollSpeed*0.01), CefEventFlags.None);
|
||||
}
|
||||
|
||||
return NativeMethods.HOOK_HANDLED;
|
||||
}
|
||||
else if (eventType == NativeMethods.WM_XBUTTONDOWN && DesktopBounds.Contains(Cursor.Position)){
|
||||
@@ -255,7 +263,7 @@ namespace TweetDuck.Core.Notification{
|
||||
string html = base.GetTweetHTML(tweet);
|
||||
|
||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||
html = injection.Inject(html);
|
||||
html = injection.InjectInto(html);
|
||||
}
|
||||
|
||||
return html;
|
||||
|
@@ -47,7 +47,7 @@ namespace TweetDuck.Core.Notification.Screenshot{
|
||||
string html = tweet.GenerateHtml("td-screenshot");
|
||||
|
||||
foreach(InjectedHTML injection in plugins.NotificationInjections){
|
||||
html = injection.Inject(html);
|
||||
html = injection.InjectInto(html);
|
||||
}
|
||||
|
||||
return html;
|
||||
|
@@ -7,7 +7,7 @@ using TweetDuck.Resources;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
sealed class TweetNotification{
|
||||
private const string DefaultHeadLayout = @"<html id='tduck' class='os-windows txt-size--14' data-td-font='medium' data-td-theme='dark'><head><meta charset='utf-8'><meta http-equiv='X-UA-Compatible' content='chrome=1'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/font.5ef884f9f9.css'><link rel='stylesheet' href='https://ton.twimg.com/tweetdeck-web/web/css/app-dark.5631e0dd42.css'><style type='text/css'>body{background:#222426}</style>";
|
||||
private const string DefaultHeadLayout = @"<html class=""scroll-v os-windows dark txt-size--14"" lang=""en-US"" id=""tduck"" data-td-font=""medium"" data-td-theme=""dark""><head><meta charset=""utf-8""><link href=""https://ton.twimg.com/tweetdeck-web/web/dist/bundle.4b1f87e09d.css"" rel=""stylesheet""><style type='text/css'>body { background: rgb(34, 36, 38) !important }</style>";
|
||||
private static readonly string CustomCSS = ScriptLoader.LoadResource("styles/notification.css") ?? string.Empty;
|
||||
public static readonly ResourceLink AppLogo = new ResourceLink("https://ton.twimg.com/tduck/avatar", ResourceHandler.FromByteArray(Properties.Resources.avatar, "image/png"));
|
||||
|
||||
|
@@ -69,7 +69,7 @@ namespace TweetDuck.Core.Other{
|
||||
MenuHandler = new ContextMenuGuide(owner),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
RequestHandler = new RequestHandlerBase(true)
|
||||
};
|
||||
|
||||
browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
|
@@ -40,14 +40,16 @@ namespace TweetDuck.Core{
|
||||
private string prevSoundNotificationPath = null;
|
||||
|
||||
public TweetDeckBrowser(FormBrowser owner, TweetDeckBridge bridge){
|
||||
RequestHandlerBrowser requestHandler = new RequestHandlerBrowser();
|
||||
|
||||
this.browser = new ChromiumWebBrowser(TwitterUtils.TweetDeckURL){
|
||||
DialogHandler = new FileDialogHandler(),
|
||||
DragHandler = new DragHandlerBrowser(),
|
||||
DragHandler = new DragHandlerBrowser(requestHandler),
|
||||
MenuHandler = new ContextMenuBrowser(owner),
|
||||
JsDialogHandler = new JavaScriptDialogHandler(),
|
||||
KeyboardHandler = new KeyboardHandlerBrowser(owner),
|
||||
LifeSpanHandler = new LifeSpanHandler(),
|
||||
RequestHandler = new RequestHandlerBrowser()
|
||||
RequestHandler = requestHandler
|
||||
};
|
||||
|
||||
this.browser.LoadingStateChanged += browser_LoadingStateChanged;
|
||||
|
@@ -28,10 +28,14 @@ namespace TweetDuck.Core.Utils{
|
||||
}
|
||||
|
||||
public static int CountOccurrences(string source, string substring){
|
||||
int count = 0, index = 0;
|
||||
if (substring.Length == 0){
|
||||
throw new ArgumentOutOfRangeException(nameof(substring), "Searched substring must not be empty.");
|
||||
}
|
||||
|
||||
int count = 0, index = 0, length = substring.Length;
|
||||
|
||||
while((index = source.IndexOf(substring, index)) != -1){
|
||||
index += substring.Length;
|
||||
index += length;
|
||||
++count;
|
||||
}
|
||||
|
||||
|
@@ -16,7 +16,7 @@ namespace TweetDuck.Data{
|
||||
this.html = html;
|
||||
}
|
||||
|
||||
public string Inject(string targetHTML){
|
||||
public string InjectInto(string targetHTML){
|
||||
int index = targetHTML.IndexOf(search, StringComparison.Ordinal);
|
||||
|
||||
if (index == -1){
|
||||
|
@@ -84,14 +84,20 @@ namespace TweetDuck.Plugins{
|
||||
|
||||
try{
|
||||
string folderPathName = new DirectoryInfo(rootFolder).FullName;
|
||||
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
|
||||
DirectoryInfo currentInfo = new DirectoryInfo(fullPath); // initially points to the file, which is convenient for the Attributes check below
|
||||
DirectoryInfo parentInfo = currentInfo.Parent;
|
||||
|
||||
while(currentInfo.Parent != null){
|
||||
if (currentInfo.Parent.FullName == folderPathName){
|
||||
while(parentInfo != null){
|
||||
if (currentInfo.Exists && currentInfo.Attributes.HasFlag(FileAttributes.ReparsePoint)){
|
||||
return string.Empty; // no reason why a plugin should have files/folders with symlinks, junctions, or any other crap
|
||||
}
|
||||
|
||||
if (parentInfo.FullName == folderPathName){
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
currentInfo = currentInfo.Parent;
|
||||
currentInfo = parentInfo;
|
||||
parentInfo = currentInfo.Parent;
|
||||
}
|
||||
}
|
||||
catch{
|
||||
|
@@ -19,7 +19,7 @@ namespace TweetDuck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.14.2";
|
||||
public const string VersionTag = "1.14.4.1";
|
||||
|
||||
public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
|
||||
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable"));
|
||||
|
@@ -42,5 +42,6 @@ using TweetDuck;
|
||||
[assembly: CLSCompliant(true)]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("TweetTest.Unit")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTests")]
|
||||
#endif
|
||||
|
@@ -9,8 +9,9 @@
|
||||
The program was built using Visual Studio 2017. Before opening the solution, please make sure you have the following workloads and components installed (optional components that are not listed can be deselected to save space):
|
||||
* **.NET desktop development**
|
||||
* .NET Framework 4 – 4.6 development tools
|
||||
* F# desktop language support
|
||||
* **Desktop development with C++**
|
||||
* VC++ 2017 v141 toolset
|
||||
* VC++ 2017 latest v141 tools
|
||||
|
||||
After opening the solution, right-click the solution and select **Restore NuGet Packages**, or manually run this command in the **Package Manager Console**:
|
||||
```
|
||||
@@ -27,7 +28,7 @@ While debugging, opening the main menu and clicking **Reload browser** automatic
|
||||
|
||||
### Release
|
||||
|
||||
Open **Batch Build**, tick all `Release` configurations with `x86` platform except for the `UnitTest` project, and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, see the [Troubleshooting](#troubleshooting) section.
|
||||
Open **Batch Build**, tick all `Release` configurations with `x86` platform, and click **Rebuild**. Check the status bar to make sure it says **Rebuild All succeeded**; if not, see the [Troubleshooting](#troubleshooting) section.
|
||||
|
||||
After the build succeeds, the `bin/x86/Release` folder will contain files intended for distribution (no debug symbols or other unnecessary files). You may package these files yourself, or see the [Installers](#installers) section for automated installer generation.
|
||||
|
||||
@@ -63,6 +64,8 @@ After the window closes, three installers will be generated inside the `bld/Outp
|
||||
|
||||
#### Notes
|
||||
|
||||
> When opening **Batch Build**, you will also see `x64` and `AnyCPU` configurations. These are visible due to what I consider a Visual Studio bug, and will not work without significant changes to the project. Manually running the `Resources/PostCefUpdate.ps1` PowerShell script modifies the downloaded CefSharp packages, and removes the invalid configurations.
|
||||
|
||||
> There is a small chance running `RUN BUILD.bat` immediately shows a resource error. If that happens, close the console window (which terminates all Inno Setup processes and leaves corrupted installers in the output folder), and run it again.
|
||||
|
||||
> Running `RUN BUILD.bat` uses about 400 MB of RAM due to high compression. You can lower this to about 140 MB by opening `gen_full.iss` and `gen_port.iss`, and changing `LZMADictionarySize=15360` to `LZMADictionarySize=4096`.
|
||||
|
BIN
Resources/Design/icon_pushpin.afdesign
Normal file
BIN
Resources/Design/icon_pushpin.afdesign
Normal file
Binary file not shown.
@@ -396,8 +396,10 @@ enabled(){
|
||||
switch(currentTheme){
|
||||
case "black":
|
||||
this.css.insert(".app-content, .app-columns-container { background-color: #444448 !important }");
|
||||
this.css.insert(".column-header-temp { background-color: transparent !important }");
|
||||
this.css.insert(".column-drag-handle { opacity: 0.5 !important }");
|
||||
this.css.insert(".column-drag-handle:hover { opacity: 1 !important }");
|
||||
this.css.insert(".column-message.is-actionable span:hover > .icon-small-valigned { filter: saturate(20) }");
|
||||
this.css.insert(".scroll-styled-v:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover), .scroll-styled-h:not(.scroll-alt)::-webkit-scrollbar-thumb:not(:hover) { background-color: #666 !important }");
|
||||
notificationScrollbarColor = "666";
|
||||
break;
|
||||
@@ -539,7 +541,7 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
||||
#tduck .js-docked-compose .js-drawer-close { margin: 20px 0 0 !important }
|
||||
#tduck .search-input-control .icon { font-size: 20px !important; top: -4px !important }
|
||||
|
||||
.column-type-icon { margin-top: -1px !important }
|
||||
.js-column-header .column-type-icon { margin-top: -1px !important }
|
||||
.inline-reply .pull-left .Button--link { margin-top: 3px !important }
|
||||
|
||||
.tweet-action-item .icon-favorite-toggle { font-size: 16px !important; }
|
||||
@@ -561,7 +563,7 @@ ${iconData.map(entry => `#tduck .icon-${entry[0]}:before{content:\"\\f0${entry[1
|
||||
if (this.config.columnWidth[0] === '/'){
|
||||
let cols = this.config.columnWidth.slice(1);
|
||||
|
||||
this.css.insert(".column { width: calc((100vw - 205px) / "+cols+" - 6px) !important }");
|
||||
this.css.insert(".column { width: calc((100vw - 205px) / "+cols+" - 6px) !important; min-width: 160px }");
|
||||
this.css.insert(".is-condensed .column { width: calc((100vw - 55px) / "+cols+" - 6px) !important }");
|
||||
}
|
||||
else{
|
||||
|
@@ -44,6 +44,8 @@
|
||||
</optgroup>
|
||||
<option disabled></option>
|
||||
<optgroup label="Dynamic width">
|
||||
<option value="/1">1 column on screen</option>
|
||||
<option value="/2">2 columns on screen</option>
|
||||
<option value="/3">3 columns on screen</option>
|
||||
<option value="/4">4 columns on screen</option>
|
||||
<option value="/5">5 columns on screen</option>
|
||||
|
@@ -51,7 +51,7 @@ enabled(){
|
||||
this.css.insert(".manage-templates-btn.active { color: #fff; box-shadow: 0 0 2px 3px #50a5e6; outline: 0; }");
|
||||
|
||||
this.css.insert("#templates-modal-wrap { width: 100%; height: 100%; padding: 49px; position: absolute; z-index: 999; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.5); }");
|
||||
this.css.insert("#templates-modal { width: 100%; height: 100%; background-color: #fff; display: flex; }");
|
||||
this.css.insert("#templates-modal { width: 100%; height: 100%; min-width: 500px; background-color: #fff; display: flex; }");
|
||||
this.css.insert("#templates-modal > div { display: flex; flex-direction: column; }");
|
||||
|
||||
this.css.insert(".templates-modal-bottom { flex: 0 0 auto; padding: 16px; }");
|
||||
@@ -68,7 +68,7 @@ enabled(){
|
||||
this.css.insert("#template-list li .icon:hover { opacity: 1; }");
|
||||
this.css.insert("#template-list li .template-actions { float: right; }");
|
||||
|
||||
this.css.insert("#template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 150px; max-width: 400px; background-color: #485865; }");
|
||||
this.css.insert("#template-editor { height: 100%; flex: 0 0 auto; width: 25vw; min-width: 225px; max-width: 400px; background-color: #485865; }");
|
||||
this.css.insert(".template-editor-form { flex: 1 1 auto; padding: 12px 16px; font-size: 14px; overflow-y: auto; }");
|
||||
this.css.insert(".template-editor-form .compose-text-title { margin: 24px 0 9px; }");
|
||||
this.css.insert(".template-editor-form .compose-text-title:first-child { margin-top: 0; }");
|
||||
@@ -261,7 +261,7 @@ enabled(){
|
||||
$(".manage-templates-btn").addClass("active");
|
||||
|
||||
let html = `
|
||||
<div id="templates-modal-wrap">
|
||||
<div id="templates-modal-wrap" class="scroll-v scroll-styled-v">
|
||||
<div id="templates-modal">
|
||||
<div id="template-list">
|
||||
<ul></ul>
|
||||
|
@@ -9,14 +9,13 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
try{
|
||||
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||
Write-Host "--------------------------"
|
||||
|
||||
if ($version.Equals("")){
|
||||
$version = (Get-Item (Join-Path $targetDir "TweetDuck.exe")).VersionInfo.FileVersion
|
||||
}
|
||||
|
||||
Write-Host "--------------------------"
|
||||
Write-Host "TweetDuck version" $version
|
||||
|
||||
Write-Host "--------------------------"
|
||||
|
||||
# Cleanup
|
||||
@@ -62,7 +61,7 @@ try{
|
||||
}
|
||||
|
||||
if ((Get-Content -Path $file -Raw).Contains("`r")){
|
||||
Throw "$file cannot contain carriage return"
|
||||
Throw "$file must not have any carriage return characters"
|
||||
}
|
||||
|
||||
Write-Host "Verified" $file.Substring($targetDir.Length)
|
||||
@@ -116,10 +115,13 @@ try{
|
||||
Rewrite-File $file $lines
|
||||
}
|
||||
|
||||
Write-Host "------------------"
|
||||
# Finished
|
||||
|
||||
$sw.Stop()
|
||||
Write-Host "------------------"
|
||||
Write-Host "Finished in" $([math]::Round($sw.Elapsed.TotalMilliseconds)) "ms"
|
||||
Write-Host "------------------"
|
||||
|
||||
}catch{
|
||||
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
|
||||
Write-Host $_
|
||||
|
@@ -1,18 +1,60 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$MainProj = "..\TweetDuck.csproj"
|
||||
$BrowserProj = "..\subprocess\TweetDuck.Browser.csproj"
|
||||
try{
|
||||
$mainProj = "..\TweetDuck.csproj"
|
||||
$browserProj = "..\subprocess\TweetDuck.Browser.csproj"
|
||||
|
||||
$Match = Select-String -Path $MainProj '<Import Project="packages\\cef\.redist\.x86\.(.*?)\\'
|
||||
$Version = $Match.Matches[0].Groups[1].Value
|
||||
$cefMatch = Select-String -Path $mainProj '<Import Project="packages\\cef\.redist\.x86\.(.*?)\\'
|
||||
$cefVersion = $cefMatch.Matches[0].Groups[1].Value
|
||||
|
||||
Copy-Item "..\packages\cef.redist.x86.${Version}\CEF\devtools_resources.pak" -Destination "..\bld\Resources\" -Force
|
||||
$sharpMatch = Select-String -Path $mainProj '<Import Project="packages\\CefSharp\.Common\.(.*?)\\'
|
||||
$sharpVersion = $sharpMatch.Matches[0].Groups[1].Value
|
||||
|
||||
$Match = Select-String -Path $MainProj '<Import Project="packages\\CefSharp\.Common\.(.*?)\\'
|
||||
$Version = $Match.Matches[0].Groups[1].Value
|
||||
$propsFiles = "..\packages\CefSharp.Common.${sharpVersion}\build\CefSharp.Common.props",
|
||||
"..\packages\CefSharp.WinForms.${sharpVersion}\build\CefSharp.WinForms.props"
|
||||
|
||||
$Contents = [IO.File]::ReadAllText($BrowserProj)
|
||||
$Contents = $Contents -Replace '(?<=<HintPath>\.\.\\packages\\CefSharp\.Common\.)(.*?)(?=\\)', $Version
|
||||
$Contents = $Contents -Replace '(?<=<Reference Include="CefSharp\.BrowserSubprocess\.Core, Version=)(\d+)', $Version.Split(".")[0]
|
||||
# Greetings
|
||||
|
||||
[IO.File]::WriteAllText($BrowserProj, $Contents)
|
||||
$title = "CEF ${cefVersion}, CefSharp ${sharpVersion}"
|
||||
|
||||
Write-Host ("-" * $title.Length)
|
||||
Write-Host $title
|
||||
Write-Host ("-" * $title.Length)
|
||||
|
||||
# Perform update
|
||||
|
||||
Write-Host "Copying dev tools to repository..."
|
||||
|
||||
Copy-Item "..\packages\cef.redist.x86.${cefVersion}\CEF\devtools_resources.pak" -Destination "..\bld\Resources\" -Force
|
||||
|
||||
Write-Host "Updating browser subprocess reference..."
|
||||
|
||||
$contents = [IO.File]::ReadAllText($browserProj)
|
||||
$contents = $contents -Replace '(?<=<HintPath>\.\.\\packages\\CefSharp\.Common\.)(.*?)(?=\\)', $sharpVersion
|
||||
$contents = $contents -Replace '(?<=<Reference Include="CefSharp\.BrowserSubprocess\.Core, Version=)(\d+)', $sharpVersion.Split(".")[0]
|
||||
|
||||
[IO.File]::WriteAllText($browserProj, $contents)
|
||||
|
||||
Write-Host "Removing x64 and AnyCPU from CefSharp props..."
|
||||
|
||||
foreach($file in $propsFiles){
|
||||
$contents = [IO.File]::ReadAllText($file)
|
||||
$contents = $contents -Replace '(?<=<When Condition=")(''\$\(Platform\)'' == ''(AnyCPU|x64)'')(?=">)', 'false'
|
||||
|
||||
[IO.File]::WriteAllText($file, $contents)
|
||||
}
|
||||
|
||||
# Finished
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Finished. Exiting in 6 seconds..."
|
||||
Start-Sleep -Seconds 6
|
||||
|
||||
}catch{
|
||||
Write-Host ""
|
||||
Write-Host "Encountered an error while running PostBuild.ps1 on line" $_.InvocationInfo.ScriptLineNumber
|
||||
Write-Host $_
|
||||
|
||||
$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
Exit 1
|
||||
}
|
||||
|
@@ -145,6 +145,19 @@
|
||||
return false;
|
||||
};
|
||||
|
||||
let isSensitive = (tweet) => {
|
||||
let main = tweet.getMainTweet && tweet.getMainTweet();
|
||||
return true if main && main.possiblySensitive; // TODO these don't show media badges when hiding sensitive media
|
||||
|
||||
let related = tweet.getRelatedTweet && tweet.getRelatedTweet();
|
||||
return true if related && related.possiblySensitive;
|
||||
|
||||
let quoted = tweet.quotedTweet;
|
||||
return true if quoted && quoted.possiblySensitive;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
let fixMedia = (html, media) => {
|
||||
return html.find("a[data-media-entity-id='"+media.mediaId+"'], .media-item").first().removeClass("is-zoomable").css("background-image", 'url("'+media.small()+'")');
|
||||
};
|
||||
@@ -160,7 +173,7 @@
|
||||
startRecentTweetTimer();
|
||||
|
||||
if (column.model.getHasNotification()){
|
||||
let sensitive = (tweet.getRelatedTweet() && tweet.getRelatedTweet().possiblySensitive || (tweet.quotedTweet && tweet.quotedTweet.possiblySensitive));
|
||||
let sensitive = isSensitive(tweet);
|
||||
let previews = $TDX.notificationMediaPreviews && (!sensitive || TD.settings.getDisplaySensitiveMedia());
|
||||
|
||||
let html = $(tweet.render({
|
||||
@@ -172,7 +185,8 @@
|
||||
isMediaPreviewLarge: false,
|
||||
isMediaPreviewCompact: false,
|
||||
isMediaPreviewInQuoted: previews,
|
||||
thumbSizeClass: "media-size-medium"
|
||||
thumbSizeClass: "media-size-medium",
|
||||
mediaPreviewSize: "medium"
|
||||
}));
|
||||
|
||||
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
|
||||
@@ -197,6 +211,10 @@
|
||||
html.find(".js-media").remove();
|
||||
}
|
||||
|
||||
html.find("a[data-full-url]").each(function(){ // bypass t.co on all links
|
||||
this.href = this.getAttribute("data-full-url");
|
||||
});
|
||||
|
||||
html.find("a[href='#']").each(function(){ // remove <a> tags around links that don't lead anywhere (such as account names the tweet replied to)
|
||||
this.outerHTML = this.innerHTML;
|
||||
});
|
||||
@@ -380,6 +398,8 @@
|
||||
let menu = $(".js-dropdown-content").children("ul").first();
|
||||
return if menu.length === 0;
|
||||
|
||||
menu.find(".update-available-item").parent().remove(); // TODO temp
|
||||
|
||||
let button = $('<li class="is-selectable" data-tweetduck><a href="#" data-action>TweetDuck</a></li>');
|
||||
button.insertBefore(menu.children(".drp-h-divider").last());
|
||||
|
||||
@@ -508,12 +528,14 @@
|
||||
obj.profileImageURL = realAvatar;
|
||||
obj.url = "https://tweetduck.chylex.com";
|
||||
|
||||
obj.entities.url.urls = [{
|
||||
url: obj.url,
|
||||
expanded_url: obj.url,
|
||||
display_url: "tweetduck.chylex.com",
|
||||
indices: [ 0, 23 ]
|
||||
}];
|
||||
if (obj.entities && obj.entities.url){
|
||||
obj.entities.url.urls = [{
|
||||
url: obj.url,
|
||||
expanded_url: obj.url,
|
||||
display_url: "tweetduck.chylex.com",
|
||||
indices: [ 0, 23 ]
|
||||
}];
|
||||
}
|
||||
}
|
||||
else if (e && e.url && e.url.urls && e.url.urls.length && e.url.urls[0].expanded_url){
|
||||
obj.url = e.url.urls[0].expanded_url;
|
||||
@@ -558,17 +580,17 @@
|
||||
let media = tweet.getMedia().find(media => media.mediaId === me.getAttribute("data-media-entity-id"));
|
||||
|
||||
if ((media.isVideo && media.service === "twitter") || media.isAnimatedGif){
|
||||
$TD.setLastRightClickInfo("video", media.chooseVideoVariant().url);
|
||||
$TD.setRightClickedLink("video", media.chooseVideoVariant().url);
|
||||
}
|
||||
else{
|
||||
$TD.setLastRightClickInfo("image", media.large());
|
||||
$TD.setRightClickedLink("image", media.large());
|
||||
}
|
||||
}
|
||||
else if (me.classList.contains("js-gif-play")){
|
||||
$TD.setLastRightClickInfo("video", $(this).closest(".js-media-gif-container").find("video").attr("src"));
|
||||
$TD.setRightClickedLink("video", $(this).closest(".js-media-gif-container").find("video").attr("src"));
|
||||
}
|
||||
else{
|
||||
$TD.setLastRightClickInfo("link", me.getAttribute("data-full-url"));
|
||||
$TD.setRightClickedLink("link", me.getAttribute("data-full-url"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1379,6 +1401,7 @@
|
||||
if (ensurePropertyExists(TD, "ui", "columns", "setupColumnScrollListeners")){
|
||||
TD.ui.columns.setupColumnScrollListeners = appendToFunction(TD.ui.columns.setupColumnScrollListeners, function(column){
|
||||
let ele = $(".js-column[data-column='"+column.model.getKey()+"']");
|
||||
return if !ele.length;
|
||||
|
||||
ele.off("onmousewheel").on("mousewheel", ".scroll-v", function(e){
|
||||
if (e.shiftKey){
|
||||
@@ -1499,10 +1522,17 @@
|
||||
//
|
||||
// Block: Disable default TweetDeck update notification.
|
||||
//
|
||||
onAppReady.push(function(){
|
||||
$(document).on("uiSuggestRefreshToggle", function(e){
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
|
||||
/*onAppReady.push(function(){
|
||||
let events = $._data(document, "events");
|
||||
delete events["uiSuggestRefreshToggle"];
|
||||
});
|
||||
|
||||
//$(".js-app-settings .icon-settings").removeClass("color-twitter-yellow"); // TODO temp
|
||||
});*/
|
||||
|
||||
//
|
||||
// Block: Disable TweetDeck metrics.
|
||||
|
@@ -3,12 +3,13 @@
|
||||
let css = $(`
|
||||
<style>
|
||||
#td-introduction-modal {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#td-introduction-modal .mdl {
|
||||
width: 90%;
|
||||
max-width: 830px;
|
||||
min-width: 515px;
|
||||
max-width: 835px;
|
||||
height: 328px;
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@
|
||||
</style>`).appendTo(document.head);
|
||||
|
||||
let ele = $(`
|
||||
<div id="td-introduction-modal" class="ovl">
|
||||
<div id="td-introduction-modal" class="ovl scroll-v scroll-styled-v">
|
||||
<div class="mdl is-inverted-dark">
|
||||
<header class="mdl-header">
|
||||
<h3 class="mdl-header-title">Welcome to TweetDuck</h3>
|
||||
|
@@ -14,14 +14,14 @@
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Hook into links to bypass default open function and t.co, and handle skipping notification when opening links.
|
||||
// Block: Hook into links to bypass default open function, and handle skipping notification when opening links.
|
||||
//
|
||||
(function(){
|
||||
const onLinkClick = function(e){
|
||||
if (e.button === 0 || e.button === 1){
|
||||
let ele = e.currentTarget;
|
||||
|
||||
$TD.openBrowser(ele.hasAttribute("data-full-url") ? ele.getAttribute("data-full-url") : ele.getAttribute("href"));
|
||||
$TD.openBrowser(ele.href);
|
||||
e.preventDefault();
|
||||
|
||||
if ($TDX.skipOnLinkClick){
|
||||
@@ -38,13 +38,6 @@
|
||||
addEventListener(links, "auxclick", onLinkClick);
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Allow bypassing of t.co in context menus.
|
||||
//
|
||||
addEventListener(links, "contextmenu", function(e){
|
||||
$TD.setLastRightClickInfo("link", e.currentTarget.getAttribute("data-full-url"));
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Expand shortened links on hover or display tooltip.
|
||||
//
|
||||
|
@@ -244,6 +244,27 @@ a[data-full-url] {
|
||||
min-width: 86px;
|
||||
}
|
||||
|
||||
/************************************************************/
|
||||
/* Fix modal dialogs breaking when window size is too small */
|
||||
/************************************************************/
|
||||
|
||||
.ovl:before, .overlay:before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ovl, .overlay {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ovl[style="display: block;"] {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
#tduck .overlay {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/*******************************************/
|
||||
/* Fix general visual issues or annoyances */
|
||||
/*******************************************/
|
||||
@@ -278,6 +299,12 @@ a[data-full-url] {
|
||||
height: calc(1em + 8px) !important;
|
||||
}
|
||||
|
||||
.column-header-temp:not(.js-column-header) {
|
||||
/* fix missing column header padding in Edit List dialog */
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.js-column-options .btn-options-tray {
|
||||
/* fix underline on buttons in column options */
|
||||
text-decoration: none !important;
|
||||
@@ -290,7 +317,7 @@ a[data-full-url] {
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .sprite-verified-mini {
|
||||
/* fix cut off badge when zoomed in */
|
||||
/* fix cut off badge in timelines */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -99px !important;
|
||||
@@ -310,11 +337,21 @@ html[data-td-font='smallest'] .tweet-detail-wrapper .badge-verified:before {
|
||||
background-position: -223px -97px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .fullname-badged:before, html[data-td-font='small'] .fullname-badged:before {
|
||||
/* fix cut off badge in follow chirps */
|
||||
margin-top: -7px !important;
|
||||
}
|
||||
|
||||
.keyboard-shortcut-list {
|
||||
/* fix keyboard navigation alignment */
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
.column-message.is-actionable span:hover > .icon-small-valigned {
|
||||
/* add a visual response when hovering individual filter icons; black theme uses a value of 20 */
|
||||
filter: saturate(10);
|
||||
}
|
||||
|
||||
.tweet-detail-wrapper .js-media-gif-container {
|
||||
/* GIFs in detail view don't trigger the pointer cursor */
|
||||
cursor: pointer;
|
||||
|
@@ -61,13 +61,25 @@ a[data-full-url] {
|
||||
/* Fix general visual issues or annoyances */
|
||||
/*******************************************/
|
||||
|
||||
html[data-td-font='smallest'] .sprite-verified-mini {
|
||||
/* fix cut off badge in timelines */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -99px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .badge-verified:before {
|
||||
/* fix cut off badge icon */
|
||||
/* fix cut off badge in notifications */
|
||||
width: 13px !important;
|
||||
height: 13px !important;
|
||||
background-position: -223px -98px !important;
|
||||
}
|
||||
|
||||
html[data-td-font='smallest'] .fullname-badged:before, html[data-td-font='small'] .fullname-badged:before {
|
||||
/* fix cut off badge in follow chirps */
|
||||
margin-top: -7px !important;
|
||||
}
|
||||
|
||||
.account-inline .username {
|
||||
vertical-align: 10% !important;
|
||||
}
|
||||
|
@@ -164,7 +164,7 @@
|
||||
<div class='tdu-buttons'>
|
||||
<button class='tdu-btn-download'>Update now</button>
|
||||
<button class='tdu-btn-later'>Remind me later</button>
|
||||
<button class='tdu-btn-ignore'>Ignore this update</button>
|
||||
<!-- TODO <button class='tdu-btn-ignore'>Ignore this update</button>-->
|
||||
</div>
|
||||
</div>
|
||||
`).appendTo(document.body).css("display", existed ? "block" : "none");
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\CefSharp.WinForms.66.0.0-CI2629\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.66.0.0-CI2629\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.66.0.0-CI2629\build\CefSharp.Common.props')" />
|
||||
@@ -15,25 +15,8 @@
|
||||
<AssemblyName>TweetDuck</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile>
|
||||
</TargetFrameworkProfile>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<StartArguments>-datafolder TweetDuckDebug</StartArguments>
|
||||
@@ -47,23 +30,15 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<Optimize>true</Optimize>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DefineConstants>
|
||||
</DefineConstants>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
@@ -330,11 +305,6 @@
|
||||
<Compile Include="Updates\Events\UpdateEventArgs.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4 Client Profile %28x86 and x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
@@ -345,11 +315,6 @@
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.4.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 4.5</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Core\FormBrowser.resx">
|
||||
|
@@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "video\Tw
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
|
||||
EndProject
|
||||
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Unit", "lib\TweetTest.Unit\TweetTest.Unit.fsproj", "{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
@@ -28,7 +30,8 @@ Global
|
||||
{B10B0017-819E-4F71-870F-8256B36A26AA}.Release|x86.Build.0 = Release|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.Build.0 = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.ActiveCfg = Release|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.ActiveCfg = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.Build.0 = Debug|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Debug|x86.Build.0 = Debug|x86
|
||||
{278B2D11-402D-44B6-B6A1-8FA67DB65565}.Release|x86.ActiveCfg = Release|x86
|
||||
@@ -37,6 +40,10 @@ Global
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.Build.0 = Release|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.Build.0 = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Release|x86.ActiveCfg = Debug|x86
|
||||
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Release|x86.Build.0 = Debug|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -10,6 +10,8 @@ using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateHandler : IDisposable{
|
||||
public const bool TemporarilyForceUpdateChecking = true;
|
||||
|
||||
public const int CheckCodeUpdatesDisabled = -1;
|
||||
public const int CheckCodeNotOnTweetDeck = -2;
|
||||
|
||||
@@ -53,7 +55,7 @@ namespace TweetDuck.Updates{
|
||||
|
||||
timer.Stop();
|
||||
|
||||
if (Program.UserConfig.EnableUpdateCheck){
|
||||
if (Program.UserConfig.EnableUpdateCheck || TemporarilyForceUpdateChecking){
|
||||
DateTime now = DateTime.Now;
|
||||
TimeSpan nextHour = now.AddSeconds(60*(60-now.Minute)-now.Second)-now;
|
||||
|
||||
@@ -67,7 +69,7 @@ namespace TweetDuck.Updates{
|
||||
}
|
||||
|
||||
public int Check(bool force){
|
||||
if (Program.UserConfig.EnableUpdateCheck || force){
|
||||
if (Program.UserConfig.EnableUpdateCheck || TemporarilyForceUpdateChecking || force){
|
||||
if (!browser.IsTweetDeckWebsite){
|
||||
return CheckCodeNotOnTweetDeck;
|
||||
}
|
||||
|
79
lib/TweetTest.Unit/Core/TestBrowserUtils.fs
Normal file
79
lib/TweetTest.Unit/Core/TestBrowserUtils.fs
Normal file
@@ -0,0 +1,79 @@
|
||||
namespace Unit.Core.BrowserUtils
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Core.Utils
|
||||
|
||||
|
||||
module CheckUrl =
|
||||
type Result = BrowserUtils.UrlCheckResult
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTP protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTPS protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("https://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts FTP protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("ftp://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts MAILTO protocol`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("mailto://someone@example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts URL with port, path, query, and hash`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://www.example.co.uk:80/path?key=abc&array[]=5#hash"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts IPv4 address`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://127.0.0.1"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts IPv6 address`` () =
|
||||
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://[2001:db8:0:0:0:ff00:42:8329]"))
|
||||
|
||||
[<Fact>]
|
||||
let ``recognizes t.co as tracking URL`` () =
|
||||
Assert.Equal(Result.Tracking, BrowserUtils.CheckUrl("http://t.co/12345"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects empty URL`` () =
|
||||
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl(""))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects missing protocol`` () =
|
||||
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("www.example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects banned protocol`` () =
|
||||
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("file://example.com"))
|
||||
|
||||
|
||||
module GetFileNameFromUrl =
|
||||
|
||||
[<Fact>]
|
||||
let ``simple file URL returns file name`` () =
|
||||
Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html"))
|
||||
|
||||
[<Fact>]
|
||||
let ``file URL with query returns file name`` () =
|
||||
Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html?version=2"))
|
||||
|
||||
[<Fact>]
|
||||
let ``file URL w/o extension returns file name`` () =
|
||||
Assert.Equal("index", BrowserUtils.GetFileNameFromUrl("http://example.com/index"))
|
||||
|
||||
[<Fact>]
|
||||
let ``file URL with trailing dot returns file name with dot`` () =
|
||||
Assert.Equal("index.", BrowserUtils.GetFileNameFromUrl("http://example.com/index."))
|
||||
|
||||
[<Fact>]
|
||||
let ``root URL returns null`` () =
|
||||
Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com"))
|
||||
|
||||
[<Fact>]
|
||||
let ``path URL returns null`` () =
|
||||
Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com/path/"))
|
118
lib/TweetTest.Unit/Core/TestStringUtils.fs
Normal file
118
lib/TweetTest.Unit/Core/TestStringUtils.fs
Normal file
@@ -0,0 +1,118 @@
|
||||
namespace Unit.Core.StringUtils
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Core.Utils
|
||||
|
||||
|
||||
module ExtractBefore =
|
||||
|
||||
[<Fact>]
|
||||
let ``empty input string returns empty string`` () =
|
||||
Assert.Equal("", StringUtils.ExtractBefore("", ' '))
|
||||
|
||||
[<Fact>]
|
||||
let ``input string w/o searched char returns input string`` () =
|
||||
Assert.Equal("abc", StringUtils.ExtractBefore("abc", ' '))
|
||||
|
||||
[<Fact>]
|
||||
let ``finding searched char returns everything before it`` () =
|
||||
Assert.Equal("abc", StringUtils.ExtractBefore("abc def ghi", ' '))
|
||||
|
||||
[<Theory>]
|
||||
[<InlineData(0, "abc")>]
|
||||
[<InlineData(3, "abc")>]
|
||||
[<InlineData(4, "abc def")>]
|
||||
[<InlineData(7, "abc def")>]
|
||||
[<InlineData(8, "abc def ghi")>]
|
||||
let ``start index works`` (startIndex: int, expectedValue: string) =
|
||||
Assert.Equal(expectedValue, StringUtils.ExtractBefore("abc def ghi", ' ', startIndex))
|
||||
|
||||
|
||||
module ParseInts =
|
||||
open System
|
||||
|
||||
[<Fact>]
|
||||
let ``empty input string returns empty collection`` () =
|
||||
Assert.Empty(StringUtils.ParseInts("", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``single integer is parsed correctly`` () =
|
||||
Assert.Equal([ 1 ], StringUtils.ParseInts("1", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``multiple integers are parsed correctly`` () =
|
||||
Assert.Equal([ -3 .. 3 ], StringUtils.ParseInts("-3,-2,-1,0,1,2,3", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``excessive delimiters are discarded`` () =
|
||||
Assert.Equal([ 1 .. 4 ], StringUtils.ParseInts(",,1,,,2,3,,4,,,", ','))
|
||||
|
||||
[<Fact>]
|
||||
let ``invalid format throws exception`` () =
|
||||
Assert.Throws<FormatException>(fun () -> StringUtils.ParseInts("1,2,a", ',') |> ignore)
|
||||
|
||||
|
||||
module ConvertPascalCaseToScreamingSnakeCase =
|
||||
|
||||
[<Fact>]
|
||||
let ``converts one word`` () =
|
||||
Assert.Equal("HELP", StringUtils.ConvertPascalCaseToScreamingSnakeCase("Help"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts two words`` () =
|
||||
Assert.Equal("HELP_ME", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts many words`` () =
|
||||
Assert.Equal("HELP_ME_PLEASE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts one uppercase abbreviation`` () =
|
||||
Assert.Equal("HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts many uppercase abbreviations`` () =
|
||||
Assert.Equal("I_LIKE_HTML_AND_CSS", StringUtils.ConvertPascalCaseToScreamingSnakeCase("ILikeHTMLAndCSS"))
|
||||
|
||||
|
||||
module ConvertRot13 =
|
||||
|
||||
[<Fact>]
|
||||
let ``ignores digits and special characters`` () =
|
||||
Assert.Equal("<123'456.789>", StringUtils.ConvertRot13("<123'456.789>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts lowercase letters correctly`` () =
|
||||
Assert.Equal("nopqrstuvwxyzabcdefghijklm", StringUtils.ConvertRot13("abcdefghijklmnopqrstuvwxyz"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts uppercase letters correctly`` () =
|
||||
Assert.Equal("NOPQRSTUVWXYZABCDEFGHIJKLM", StringUtils.ConvertRot13("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
|
||||
|
||||
[<Fact>]
|
||||
let ``converts mixed character types correctly`` () =
|
||||
Assert.Equal("Uryyb, jbeyq! :)", StringUtils.ConvertRot13("Hello, world! :)"))
|
||||
|
||||
|
||||
module CountOccurrences =
|
||||
open System
|
||||
|
||||
[<Fact>]
|
||||
let ``empty input string returns zero`` () =
|
||||
Assert.Equal(0, StringUtils.CountOccurrences("", "a"))
|
||||
|
||||
[<Fact>]
|
||||
let ``empty searched string throws`` () =
|
||||
Assert.Throws<ArgumentOutOfRangeException>(fun () -> StringUtils.CountOccurrences("hello", "") |> ignore)
|
||||
|
||||
[<Fact>]
|
||||
let ``counts single letter characters correctly`` () =
|
||||
Assert.Equal(3, StringUtils.CountOccurrences("hello world", "l"))
|
||||
|
||||
[<Fact>]
|
||||
let ``counts longer substrings correctly`` () =
|
||||
Assert.Equal(2, StringUtils.CountOccurrences("hello and welcome in hell", "hell"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not count overlapping substrings`` () =
|
||||
Assert.Equal(2, StringUtils.CountOccurrences("aaaaa", "aa"))
|
112
lib/TweetTest.Unit/Core/TestTwitterUtils.fs
Normal file
112
lib/TweetTest.Unit/Core/TestTwitterUtils.fs
Normal file
@@ -0,0 +1,112 @@
|
||||
namespace Unit.Core.TwitterUtils
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Core.Utils
|
||||
|
||||
|
||||
[<Collection("RegexAccount")>]
|
||||
module RegexAccount_IsMatch =
|
||||
let isMatch = TwitterUtils.RegexAccount.IsMatch
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTP protocol`` () =
|
||||
Assert.True(isMatch("http://twitter.com/chylexmc"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts HTTPS protocol`` () =
|
||||
Assert.True(isMatch("https://twitter.com/chylexmc"))
|
||||
|
||||
[<Fact>]
|
||||
let ``accepts trailing slash`` () =
|
||||
Assert.True(isMatch("https://twitter.com/chylexmc/"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects URL with query`` () =
|
||||
Assert.False(isMatch("https://twitter.com/chylexmc?query"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rejects URL with extra path`` () =
|
||||
Assert.False(isMatch("https://twitter.com/chylexmc/status/123"))
|
||||
|
||||
[<Theory>]
|
||||
[<InlineData("signup")>]
|
||||
[<InlineData("tos")>]
|
||||
[<InlineData("privacy")>]
|
||||
[<InlineData("search")>]
|
||||
[<InlineData("search?query")>]
|
||||
[<InlineData("search-home")>]
|
||||
[<InlineData("search-advanced")>]
|
||||
let ``rejects reserved page names`` (name: string) =
|
||||
Assert.False(isMatch("https://twitter.com/"+name))
|
||||
|
||||
[<Theory>]
|
||||
[<InlineData("tosser")>]
|
||||
[<InlineData("searching")>]
|
||||
let ``accepts accounts starting with reserved page names`` (name: string) =
|
||||
Assert.True(isMatch("https://twitter.com/"+name))
|
||||
|
||||
|
||||
[<Collection("RegexAccount")>]
|
||||
module RegexAccount_Match =
|
||||
let extract str = TwitterUtils.RegexAccount.Match(str).Groups.[1].Value
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts account name from simple URL`` () =
|
||||
Assert.Equal("_abc_DEF_123", extract("https://twitter.com/_abc_DEF_123"))
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts account name from URL with trailing slash`` () =
|
||||
Assert.Equal("_abc_DEF_123", extract("https://twitter.com/_abc_DEF_123/"))
|
||||
|
||||
|
||||
module GetMediaLink_Default =
|
||||
let getMediaLinkDefault url = TwitterUtils.GetMediaLink(url, TwitterUtils.ImageQuality.Default)
|
||||
let domain = "https://pbs.twimg.com"
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify URL w/o extension`` () =
|
||||
Assert.Equal(domain+"/media/123", getMediaLinkDefault(domain+"/media/123"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify URL w/o quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg", getMediaLinkDefault(domain+"/media/123.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify URL with quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg:small", getMediaLinkDefault(domain+"/media/123.jpg:small"))
|
||||
|
||||
|
||||
module GetMediaLink_Orig =
|
||||
let getMediaLinkOrig url = TwitterUtils.GetMediaLink(url, TwitterUtils.ImageQuality.Orig)
|
||||
let domain = "https://pbs.twimg.com"
|
||||
|
||||
[<Fact>]
|
||||
let ``appends :orig to valid URL w/o quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg:orig", getMediaLinkOrig(domain+"/media/123.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rewrites :orig into valid URL with quality suffix`` () =
|
||||
Assert.Equal(domain+"/media/123.jpg:orig", getMediaLinkOrig(domain+"/media/123.jpg:small"))
|
||||
|
||||
[<Fact>]
|
||||
let ``does not modify unknown URL w/o quality suffix`` () =
|
||||
Assert.Equal(domain+"/profile_images/123.jpg", getMediaLinkOrig(domain+"/profile_images/123.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``rewrites :orig into unknown URL with quality suffix`` () =
|
||||
Assert.Equal(domain+"/profile_images/123.jpg:orig", getMediaLinkOrig(domain+"/profile_images/123.jpg:small"))
|
||||
|
||||
|
||||
module GetImageFileName =
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts file name from URL w/o quality suffix`` () =
|
||||
Assert.Equal("test.jpg", TwitterUtils.GetImageFileName("http://example.com/test.jpg"))
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts file name from URL with quality suffix`` () =
|
||||
Assert.Equal("test.jpg", TwitterUtils.GetImageFileName("http://example.com/test.jpg:orig"))
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts file name from URL with a port`` () =
|
||||
Assert.Equal("test.jpg", TwitterUtils.GetImageFileName("http://example.com:80/test.jpg"))
|
49
lib/TweetTest.Unit/Data/TestInjectedHTML.fs
Normal file
49
lib/TweetTest.Unit/Data/TestInjectedHTML.fs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace Unit.Data.InjectedHTML
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
|
||||
|
||||
module Inject =
|
||||
let before = InjectedHTML.Position.Before
|
||||
let after = InjectedHTML.Position.After
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string before searched string works`` () =
|
||||
Assert.Equal("<p>source[left]<br>code</p>", InjectedHTML(before, "<br>", "[left]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string after searched string works`` () =
|
||||
Assert.Equal("<p>source<br>[right]code</p>", InjectedHTML(after, "<br>", "[right]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string at the beginning works`` () =
|
||||
Assert.Equal("[start]<p>source<br>code</p>", InjectedHTML(before, "<p>", "[start]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting string at the end works`` () =
|
||||
Assert.Equal("<p>source<br>code</p>[end]", InjectedHTML(after, "</p>", "[end]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injection only triggers for first occurrence of searched string`` () =
|
||||
Assert.Equal("<p>source[left]<br>code</p><br>", InjectedHTML(before, "<br>", "[left]").InjectInto("<p>source<br>code</p><br>"))
|
||||
Assert.Equal("<p>source<br>[right]code</p><br>", InjectedHTML(after, "<br>", "[right]").InjectInto("<p>source<br>code</p><br>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``empty searched string injects at the beginning`` () =
|
||||
Assert.Equal("[start]<p>source<br>code</p>", InjectedHTML(before, "", "[start]").InjectInto("<p>source<br>code</p>"))
|
||||
Assert.Equal("[start]<p>source<br>code</p>", InjectedHTML(after, "", "[start]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``injecting empty string does not modify source`` () =
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(before, "<br>", "").InjectInto("<p>source<br>code</p>"))
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(after, "<br>", "").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``failed match does not modify source`` () =
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(before, "<wrong>", "[left]").InjectInto("<p>source<br>code</p>"))
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(after, "<wrong>", "[right]").InjectInto("<p>source<br>code</p>"))
|
||||
|
||||
[<Fact>]
|
||||
let ``invalid position does not modify source`` () =
|
||||
Assert.Equal("<p>source<br>code</p>", InjectedHTML(enum<_>(1000), "<br>", "[somewhere]").InjectInto("<p>source<br>code</p>"))
|
57
lib/TweetTest.Unit/Data/TestResult.fs
Normal file
57
lib/TweetTest.Unit/Data/TestResult.fs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace Unit.Data.Result
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
open System
|
||||
|
||||
|
||||
module Result_WithValue =
|
||||
let result = Result<int>(10)
|
||||
|
||||
[<Fact>]
|
||||
let ``HasValue returns true`` () =
|
||||
Assert.True(result.HasValue)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Value returns the provided value`` () =
|
||||
Assert.Equal(10, result.Value)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Exception throws`` () =
|
||||
Assert.Throws<InvalidOperationException>(fun () -> result.Exception |> ignore)
|
||||
|
||||
[<Fact>]
|
||||
let ``Handle calls the correct callback`` () =
|
||||
let passTest = fun _ -> ()
|
||||
let failTest = fun _ -> Assert.True(false)
|
||||
result.Handle(Action<_>(passTest), Action<_>(failTest))
|
||||
|
||||
[<Fact>]
|
||||
let ``Select returns another valid Result`` () =
|
||||
Assert.Equal(20, result.Select(fun x -> x * 2).Value)
|
||||
|
||||
|
||||
module Result_WithException =
|
||||
let result = Result<int>(IndexOutOfRangeException("bad"))
|
||||
|
||||
[<Fact>]
|
||||
let ``HasValue returns false`` () =
|
||||
Assert.False(result.HasValue)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Value throws`` () =
|
||||
Assert.Throws<InvalidOperationException>(fun () -> result.Value |> ignore)
|
||||
|
||||
[<Fact>]
|
||||
let ``accessing Exception returns the provided exception`` () =
|
||||
Assert.IsType<IndexOutOfRangeException>(result.Exception)
|
||||
|
||||
[<Fact>]
|
||||
let ``Handle calls the correct callback`` () =
|
||||
let passTest = fun _ -> ()
|
||||
let failTest = fun _ -> Assert.True(false)
|
||||
result.Handle(Action<_>(failTest), Action<_>(passTest))
|
||||
|
||||
[<Fact>]
|
||||
let ``Select returns a Result with the same Exception`` () =
|
||||
Assert.Same(result.Exception, result.Select(fun x -> x * 2).Exception)
|
109
lib/TweetTest.Unit/TweetTest.Unit.fsproj
Normal file
109
lib/TweetTest.Unit/TweetTest.Unit.fsproj
Normal file
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props')" />
|
||||
<Import Project="..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props')" />
|
||||
<Import Project="..\..\packages\xunit.core.2.3.1\build\xunit.core.props" Condition="Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>eee1071a-28fa-48b1-82a1-9cbdc5c3f2c3</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>TweetTest.Unit</RootNamespace>
|
||||
<AssemblyName>TweetTest.Unit</AssemblyName>
|
||||
<UseStandardResourceNames>True</UseStandardResourceNames>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFSharpCoreVersion>4.4.3.0</TargetFSharpCoreVersion>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Name>TweetTest.Unit</Name>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<Tailcalls>false</Tailcalls>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<WarningLevel>3</WarningLevel>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(VisualStudioVersion)' == '11.0'">
|
||||
<PropertyGroup Condition=" '$(FSharpTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets') ">
|
||||
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
|
||||
</PropertyGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<PropertyGroup Condition=" '$(FSharpTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets') ">
|
||||
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
|
||||
</PropertyGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
<Import Project="$(FSharpTargetsPath)" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.3.1\build\xunit.core.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.3.1\build\xunit.core.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.3.1\build\net20\xunit.runner.visualstudio.props'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\packages\xunit.core.2.3.1\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.3.1\build\xunit.core.targets')" />
|
||||
<Import Project="..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.7.2\build\net45\Microsoft.Net.Test.Sdk.targets')" />
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config" />
|
||||
<Compile Include="Core\TestBrowserUtils.fs" />
|
||||
<Compile Include="Core\TestStringUtils.fs" />
|
||||
<Compile Include="Core\TestTwitterUtils.fs" />
|
||||
<Compile Include="Data\TestInjectedHTML.fs" />
|
||||
<Compile Include="Data\TestResult.fs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.CodeCoverage.Shim">
|
||||
<HintPath>..\..\packages\Microsoft.CodeCoverage.1.0.3\lib\netstandard1.0\Microsoft.VisualStudio.CodeCoverage.Shim.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="FSharp.Core">
|
||||
<Name>FSharp.Core</Name>
|
||||
<AssemblyName>FSharp.Core.dll</AssemblyName>
|
||||
<HintPath>$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\$(TargetFSharpCoreVersion)\FSharp.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="xunit.abstractions">
|
||||
<HintPath>..\..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.assert">
|
||||
<HintPath>..\..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.core">
|
||||
<HintPath>..\..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.execution.desktop">
|
||||
<HintPath>..\..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll</HintPath>
|
||||
</Reference>
|
||||
<ProjectReference Include="..\..\TweetDuck.csproj">
|
||||
<Name>TweetDuck</Name>
|
||||
<Project>{2389a7cd-e0d3-4706-8294-092929a33a2d}</Project>
|
||||
<Private>True</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
13
lib/TweetTest.Unit/packages.config
Normal file
13
lib/TweetTest.Unit/packages.config
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.CodeCoverage" version="1.0.3" targetFramework="net452" />
|
||||
<package id="Microsoft.NET.Test.Sdk" version="15.7.2" targetFramework="net452" />
|
||||
<package id="xunit" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.abstractions" version="2.0.1" targetFramework="net452" />
|
||||
<package id="xunit.analyzers" version="0.9.0" targetFramework="net452" />
|
||||
<package id="xunit.assert" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.core" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.core" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.extensibility.execution" version="2.3.1" targetFramework="net452" />
|
||||
<package id="xunit.runner.visualstudio" version="2.3.1" targetFramework="net452" developmentDependency="true" />
|
||||
</packages>
|
@@ -1,53 +0,0 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core{
|
||||
[TestClass]
|
||||
public class TestBrowserUtils{
|
||||
[TestMethod]
|
||||
public void TestIsValidUrl(){
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com")); // base
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://www.google.com")); // www.
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.co.uk")); // co.uk
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("https://google.com")); // https
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("ftp://google.com")); // ftp
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("mailto:someone@google.com")); // mailto
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/")); // trailing slash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?")); // trailing question mark
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x")); // parameters
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/#hash")); // parameters + hash
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://google.com/?a=5&b=x#hash")); // parameters + hash
|
||||
|
||||
foreach(string tld in new string[]{ "accountants", "blackfriday", "cancerresearch", "coffee", "cool", "foo", "travelersinsurance" }){
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Fine, BrowserUtils.CheckUrl("http://test."+tld)); // long and unusual TLDs
|
||||
}
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("http://t.co/abc")); // tracking
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Tracking, BrowserUtils.CheckUrl("https://t.co/abc")); // tracking
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer")); // file
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("explorer.exe")); // file
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("://explorer.exe")); // file-sorta
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("file://explorer.exe")); // file-proper
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("")); // empty
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("lol")); // random
|
||||
|
||||
Assert.AreEqual(BrowserUtils.UrlCheckResult.Invalid, BrowserUtils.CheckUrl("gopher://nobody.cares")); // lmao rekt
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestGetFileNameFromUrl(){
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html"));
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html?"));
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html?param1=abc¶m2=false"));
|
||||
|
||||
Assert.AreEqual("index", BrowserUtils.GetFileNameFromUrl("http://test.com/index"));
|
||||
Assert.AreEqual("index.", BrowserUtils.GetFileNameFromUrl("http://test.com/index."));
|
||||
|
||||
Assert.IsNull(BrowserUtils.GetFileNameFromUrl("http://test.com/"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core{
|
||||
[TestClass]
|
||||
public class TestStringUtils{
|
||||
[TestMethod]
|
||||
public void TestExtractBefore(){
|
||||
Assert.AreEqual("missing", StringUtils.ExtractBefore("missing", '_'));
|
||||
Assert.AreEqual("", StringUtils.ExtractBefore("_empty", '_'));
|
||||
Assert.AreEqual("some", StringUtils.ExtractBefore("some_text", '_'));
|
||||
Assert.AreEqual("first", StringUtils.ExtractBefore("first_separator_only", '_'));
|
||||
Assert.AreEqual("start_index", StringUtils.ExtractBefore("start_index_test", '_', 8));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestParseInts(){
|
||||
CollectionAssert.AreEqual(new int[0], StringUtils.ParseInts("", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1 }, StringUtils.ParseInts("1", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3,", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts(",1,2,,3,", ','));
|
||||
CollectionAssert.AreEqual(new int[]{ -50, 50 }, StringUtils.ParseInts("-50,50", ','));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestConvertPascalCaseToScreamingSnakeCase(){
|
||||
Assert.AreEqual("HELP", StringUtils.ConvertPascalCaseToScreamingSnakeCase("Help"));
|
||||
Assert.AreEqual("HELP_ME", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe"));
|
||||
Assert.AreEqual("HELP_ME_PLEASE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease"));
|
||||
|
||||
Assert.AreEqual("HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode"));
|
||||
Assert.AreEqual("CHECK_OUT_MY_HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("CheckOutMyHTMLCode"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,57 +0,0 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core{
|
||||
[TestClass]
|
||||
public class TestTwitterUtils{
|
||||
[TestMethod]
|
||||
public void TestAccountRegex(){
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc/"));
|
||||
|
||||
Assert.AreEqual("chylexmc", TwitterUtils.RegexAccount.Match("http://twitter.com/chylexmc").Groups[1].Value);
|
||||
Assert.AreEqual("123", TwitterUtils.RegexAccount.Match("http://twitter.com/123").Groups[1].Value);
|
||||
Assert.AreEqual("_", TwitterUtils.RegexAccount.Match("http://twitter.com/_").Groups[1].Value);
|
||||
|
||||
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("http://twitter.com/Abc_123").Groups[1].Value);
|
||||
Assert.AreEqual("Abc_123", TwitterUtils.RegexAccount.Match("https://twitter.com/Abc_123/").Groups[1].Value);
|
||||
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://twitter.com/chylexmc/status"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("http://nottwitter.com/chylexmc"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/chylexmc?"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("www.twitter.com/chylexmc"));
|
||||
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/signup"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/tos"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/privacy"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search?query"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search-home"));
|
||||
Assert.IsFalse(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/search-advanced"));
|
||||
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/tosser"));
|
||||
Assert.IsTrue(TwitterUtils.RegexAccount.IsMatch("https://twitter.com/searching"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestImageQualityLink(){
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/profile_images/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:small", TwitterUtils.ImageQuality.Orig));
|
||||
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Default));
|
||||
Assert.AreEqual("https://pbs.twimg.com/media/123.jpg:orig", TwitterUtils.GetMediaLink("https://pbs.twimg.com/media/123.jpg:large", TwitterUtils.ImageQuality.Orig));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Data;
|
||||
|
||||
namespace UnitTests.Data{
|
||||
[TestClass]
|
||||
public class TestInjectedHTML{
|
||||
private static IEnumerable<InjectedHTML.Position> Positions => Enum.GetValues(typeof(InjectedHTML.Position)).Cast<InjectedHTML.Position>();
|
||||
|
||||
[TestMethod]
|
||||
public void TestFailedMatches(){
|
||||
foreach(var pos in Positions){
|
||||
Assert.AreEqual(string.Empty, new InjectedHTML(pos, "b", "b").Inject(string.Empty));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, "b", "b").Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptySearch(){
|
||||
foreach(var pos in Positions){
|
||||
Assert.AreEqual("b", new InjectedHTML(pos, string.Empty, "b").Inject(string.Empty));
|
||||
Assert.AreEqual("baaaa", new InjectedHTML(pos, string.Empty, "b").Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptyHTML(){
|
||||
foreach(var pos in Positions){
|
||||
Assert.AreEqual(string.Empty, new InjectedHTML(pos, string.Empty, string.Empty).Inject(string.Empty));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, string.Empty, string.Empty).Inject("aaaa"));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, "a", string.Empty).Inject("aaaa"));
|
||||
Assert.AreEqual("aaaa", new InjectedHTML(pos, "b", string.Empty).Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestInvalidPosition(){
|
||||
Assert.AreEqual("aaaa", new InjectedHTML((InjectedHTML.Position)(Positions.Count()+1), "a", "b").Inject("aaaa"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPositions(){
|
||||
Assert.AreEqual("aaabcxaaa", new InjectedHTML(InjectedHTML.Position.Before, "x", "bc").Inject("aaaxaaa"));
|
||||
Assert.AreEqual("aaaxbcaaa", new InjectedHTML(InjectedHTML.Position.After, "x", "bc").Inject("aaaxaaa"));
|
||||
|
||||
Assert.AreEqual("bcxaaa", new InjectedHTML(InjectedHTML.Position.Before, "x", "bc").Inject("xaaa"));
|
||||
Assert.AreEqual("xbcaaa", new InjectedHTML(InjectedHTML.Position.After, "x", "bc").Inject("xaaa"));
|
||||
|
||||
Assert.AreEqual("aaabcx", new InjectedHTML(InjectedHTML.Position.Before, "x", "bc").Inject("aaax"));
|
||||
Assert.AreEqual("aaaxbc", new InjectedHTML(InjectedHTML.Position.After, "x", "bc").Inject("aaax"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFirstOccurrence(){
|
||||
Assert.AreEqual("bcaaaa", new InjectedHTML(InjectedHTML.Position.Before, "a", "bc").Inject("aaaa"));
|
||||
Assert.AreEqual("abcaaa", new InjectedHTML(InjectedHTML.Position.After, "a", "bc").Inject("aaaa"));
|
||||
|
||||
Assert.AreEqual("bcaaaa", new InjectedHTML(InjectedHTML.Position.Before, "aa", "bc").Inject("aaaa"));
|
||||
Assert.AreEqual("aabcaa", new InjectedHTML(InjectedHTML.Position.After, "aa", "bc").Inject("aaaa"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,11 +24,6 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
@@ -49,13 +44,9 @@
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="Configuration\TestUserConfig.cs" />
|
||||
<Compile Include="Core\TestStringUtils.cs" />
|
||||
<Compile Include="Core\TestTwitterUtils.cs" />
|
||||
<Compile Include="Data\TestCombinedFileStream.cs" />
|
||||
<Compile Include="Core\TestBrowserUtils.cs" />
|
||||
<Compile Include="Data\TestCommandLineArgs.cs" />
|
||||
<Compile Include="Data\TestFileSerializer.cs" />
|
||||
<Compile Include="Data\TestInjectedHTML.cs" />
|
||||
<Compile Include="Data\TestTwoKeyDictionary.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="UnitTestIO.cs" />
|
||||
|
Reference in New Issue
Block a user